Repository: darkforest-eth/client Branch: master Commit: 009e6438d9a8 Files: 531 Total size: 2.1 MB Directory structure: gitextract_e9nah5ci/ ├── .babelrc ├── .dockerignore ├── .eslintignore ├── .gitignore ├── .nvmrc ├── .prettierignore ├── LICENSE ├── README.md ├── docs/ │ ├── README.md │ ├── classes/ │ │ ├── Backend_GameLogic_CaptureZoneGenerator.CaptureZoneGenerator.md │ │ ├── Backend_GameLogic_ContractsAPI.ContractsAPI.md │ │ ├── Backend_GameLogic_GameManager.default.md │ │ ├── Backend_GameLogic_GameObjects.GameObjects.md │ │ ├── Backend_GameLogic_GameUIManager.default.md │ │ ├── Backend_GameLogic_InitialGameStateDownloader.InitialGameStateDownloader.md │ │ ├── Backend_GameLogic_LayeredMap.LayeredMap.md │ │ ├── Backend_GameLogic_PluginManager.PluginManager.md │ │ ├── Backend_GameLogic_PluginManager.ProcessInfo.md │ │ ├── Backend_GameLogic_TutorialManager.default.md │ │ ├── Backend_GameLogic_ViewportEntities.ViewportEntities.md │ │ ├── Backend_Miner_MinerManager.HomePlanetMinerChunkStore.md │ │ ├── Backend_Miner_MinerManager.default.md │ │ ├── Backend_Miner_MiningPatterns.SpiralPattern.md │ │ ├── Backend_Miner_MiningPatterns.SwissCheesePattern.md │ │ ├── Backend_Miner_MiningPatterns.TowardsCenterPattern.md │ │ ├── Backend_Miner_MiningPatterns.TowardsCenterPatternV2.md │ │ ├── Backend_Network_EventLogger.EventLogger.md │ │ ├── Backend_Storage_PersistentChunkStore.default.md │ │ ├── Backend_Storage_ReaderDataStore.default.md │ │ ├── Backend_Utils_SnarkArgsHelper.default.md │ │ ├── Backend_Utils_Wrapper.Wrapper.md │ │ ├── Frontend_Components_Btn.DarkForestButton.md │ │ ├── Frontend_Components_Btn.DarkForestShortcutButton.md │ │ ├── Frontend_Components_Btn.ShortcutPressedEvent.md │ │ ├── Frontend_Components_Input.DarkForestCheckbox.md │ │ ├── Frontend_Components_Input.DarkForestColorInput.md │ │ ├── Frontend_Components_Input.DarkForestNumberInput.md │ │ ├── Frontend_Components_Input.DarkForestTextInput.md │ │ ├── Frontend_Components_Modal.DarkForestModal.md │ │ ├── Frontend_Components_Modal.PositionChangedEvent.md │ │ ├── Frontend_Components_Slider.DarkForestSlider.md │ │ ├── Frontend_Components_Slider.DarkForestSliderHandle.md │ │ ├── Frontend_Game_ModalManager.default.md │ │ ├── Frontend_Game_NotificationManager.default.md │ │ ├── Frontend_Game_Viewport.default.md │ │ ├── Frontend_Panes_Lobbies_Reducer.InvalidConfigError.md │ │ ├── Frontend_Renderers_Artifacts_ArtifactRenderer.ArtifactRenderer.md │ │ ├── Frontend_Renderers_GifRenderer.GifRenderer.md │ │ ├── Frontend_Utils_UIEmitter.default.md │ │ ├── Frontend_Views_DFErrorBoundary.DFErrorBoundary.md │ │ └── Frontend_Views_GenericErrorBoundary.GenericErrorBoundary.md │ ├── enums/ │ │ ├── Backend_GameLogic_GameManager.GameManagerEvent.md │ │ ├── Backend_GameLogic_GameUIManager.GameUIManagerEvent.md │ │ ├── Backend_GameLogic_TutorialManager.TutorialManagerEvent.md │ │ ├── Backend_GameLogic_TutorialManager.TutorialState.md │ │ ├── Backend_Miner_MinerManager.MinerManagerEvent.md │ │ ├── Backend_Miner_MiningPatterns.MiningPatternType.md │ │ ├── Backend_Network_EventLogger.EventType.md │ │ ├── Backend_Network_UtilityServerAPI.EmailResponse.md │ │ ├── Backend_Storage_ReaderDataStore.SinglePlanetDataStoreEvent.md │ │ ├── Frontend_Components_Email.EmailCTAMode.md │ │ ├── Frontend_Components_GameLandingPageComponents.InitRenderState.md │ │ ├── Frontend_Game_NotificationManager.NotificationManagerEvent.md │ │ ├── Frontend_Game_NotificationManager.NotificationType.md │ │ ├── Frontend_Pages_LandingPage.LandingPageZIndex.md │ │ ├── Frontend_Pages_UnsubscribePage.LandingPageZIndex.md │ │ ├── Frontend_Utils_BrowserChecks.Incompatibility.md │ │ ├── Frontend_Utils_TerminalTypes.TerminalTextStyle.md │ │ ├── Frontend_Utils_UIEmitter.UIEmitterEvent.md │ │ ├── Frontend_Utils_constants.DFZIndex.md │ │ ├── Frontend_Views_PlanetNotifications.PlanetNotifType.md │ │ ├── types_darkforest_api_ContractsAPITypes.ContractEvent.md │ │ ├── types_darkforest_api_ContractsAPITypes.ContractsAPIEvent.md │ │ ├── types_darkforest_api_ContractsAPITypes.InitArgIdxs.md │ │ ├── types_darkforest_api_ContractsAPITypes.MoveArgIdxs.md │ │ ├── types_darkforest_api_ContractsAPITypes.PlanetEventType.md │ │ ├── types_darkforest_api_ContractsAPITypes.UpgradeArgIdxs.md │ │ ├── types_darkforest_api_ContractsAPITypes.ZKArgIdx.md │ │ └── types_global_GlobalTypes.StatIdx.md │ ├── interfaces/ │ │ ├── Backend_GameLogic_ArrivalUtils.PlanetDiff.md │ │ ├── Backend_GameLogic_InitialGameStateDownloader.InitialGameState.md │ │ ├── Backend_Miner_MiningPatterns.MiningPattern.md │ │ ├── Backend_Network_AccountManager.Account.md │ │ ├── Backend_Plugins_EmbeddedPluginLoader.EmbeddedPlugin.md │ │ ├── Backend_Plugins_PluginProcess.PluginProcess.md │ │ ├── Backend_Plugins_SerializedPlugin.SerializedPlugin.md │ │ ├── Frontend_Components_TextLoadingBar.LoadingBarHandle.md │ │ ├── Frontend_Panes_Lobbies_LobbiesUtils.LobbiesPaneProps.md │ │ ├── Frontend_Panes_Tooltip.TooltipProps.md │ │ ├── Frontend_Panes_Tooltip.TooltipTriggerProps.md │ │ ├── Frontend_Utils_EmitterUtils.Diff.md │ │ ├── Frontend_Views_ModalPane.ModalFrame.md │ │ ├── Frontend_Views_ModalPane.ModalHandle.md │ │ ├── Frontend_Views_Share.ShareProps.md │ │ ├── Frontend_Views_Terminal.TerminalHandle.md │ │ ├── Frontend_Views_Terminal.TerminalProps.md │ │ ├── types_darkforest_api_ChunkStoreTypes.ChunkStore.md │ │ ├── types_darkforest_api_ChunkStoreTypes.PersistedChunk.md │ │ ├── types_darkforest_api_ChunkStoreTypes.PersistedLocation.md │ │ ├── types_darkforest_api_ContractsAPITypes.ContractConstants.md │ │ ├── types_global_GlobalTypes.ClaimCountdownInfo.md │ │ ├── types_global_GlobalTypes.MinerWorkerMessage.md │ │ └── types_global_GlobalTypes.RevealCountdownInfo.md │ └── modules/ │ ├── Backend_GameLogic_ArrivalUtils.md │ ├── Backend_GameLogic_CaptureZoneGenerator.md │ ├── Backend_GameLogic_ContractsAPI.md │ ├── Backend_GameLogic_GameManager.md │ ├── Backend_GameLogic_GameObjects.md │ ├── Backend_GameLogic_GameUIManager.md │ ├── Backend_GameLogic_InitialGameStateDownloader.md │ ├── Backend_GameLogic_LayeredMap.md │ ├── Backend_GameLogic_PluginManager.md │ ├── Backend_GameLogic_TutorialManager.md │ ├── Backend_GameLogic_ViewportEntities.md │ ├── Backend_Miner_ChunkUtils.md │ ├── Backend_Miner_MinerManager.md │ ├── Backend_Miner_MiningPatterns.md │ ├── Backend_Miner_permutation.md │ ├── Backend_Network_AccountManager.md │ ├── Backend_Network_Blockchain.md │ ├── Backend_Network_EventLogger.md │ ├── Backend_Network_LeaderboardApi.md │ ├── Backend_Network_MessageAPI.md │ ├── Backend_Network_NetworkHealthApi.md │ ├── Backend_Network_UtilityServerAPI.md │ ├── Backend_Plugins_EmbeddedPluginLoader.md │ ├── Backend_Plugins_PluginProcess.md │ ├── Backend_Plugins_PluginTemplate.md │ ├── Backend_Plugins_SerializedPlugin.md │ ├── Backend_Storage_PersistentChunkStore.md │ ├── Backend_Storage_ReaderDataStore.md │ ├── Backend_Utils_Animation.md │ ├── Backend_Utils_Coordinates.md │ ├── Backend_Utils_SnarkArgsHelper.md │ ├── Backend_Utils_Utils.md │ ├── Backend_Utils_WhitelistSnarkArgsHelper.md │ ├── Backend_Utils_Wrapper.md │ ├── Frontend_Components_AncientLabel.md │ ├── Frontend_Components_ArtifactImage.md │ ├── Frontend_Components_BiomeAnims.md │ ├── Frontend_Components_Btn.md │ ├── Frontend_Components_Button.md │ ├── Frontend_Components_CapturePlanetButton.md │ ├── Frontend_Components_CoreUI.md │ ├── Frontend_Components_Corner.md │ ├── Frontend_Components_DisplayGasPrices.md │ ├── Frontend_Components_Email.md │ ├── Frontend_Components_GameLandingPageComponents.md │ ├── Frontend_Components_GameWindowComponents.md │ ├── Frontend_Components_Icons.md │ ├── Frontend_Components_Input.md │ ├── Frontend_Components_Labels_ArtifactLabels.md │ ├── Frontend_Components_Labels_BiomeLabels.md │ ├── Frontend_Components_Labels_KeywordLabels.md │ ├── Frontend_Components_Labels_Labels.md │ ├── Frontend_Components_Labels_LavaLabel.md │ ├── Frontend_Components_Labels_LegendaryLabel.md │ ├── Frontend_Components_Labels_MythicLabel.md │ ├── Frontend_Components_Labels_PlanetLabels.md │ ├── Frontend_Components_Labels_SpacetimeRipLabel.md │ ├── Frontend_Components_Labels_WastelandLabel.md │ ├── Frontend_Components_LoadingSpinner.md │ ├── Frontend_Components_MaybeShortcutButton.md │ ├── Frontend_Components_MineArtifactButton.md │ ├── Frontend_Components_Modal.md │ ├── Frontend_Components_OpenPaneButtons.md │ ├── Frontend_Components_PluginModal.md │ ├── Frontend_Components_ReadMore.md │ ├── Frontend_Components_RemoteModal.md │ ├── Frontend_Components_Row.md │ ├── Frontend_Components_Slider.md │ ├── Frontend_Components_Text.md │ ├── Frontend_Components_TextLoadingBar.md │ ├── Frontend_Components_TextPreview.md │ ├── Frontend_Components_Theme.md │ ├── Frontend_Components_TimeUntil.md │ ├── Frontend_Game_ControllableCanvas.md │ ├── Frontend_Game_ModalManager.md │ ├── Frontend_Game_NotificationManager.md │ ├── Frontend_Game_Popups.md │ ├── Frontend_Game_Viewport.md │ ├── Frontend_Pages_App.md │ ├── Frontend_Pages_CreateLobby.md │ ├── Frontend_Pages_EventsPage.md │ ├── Frontend_Pages_GameLandingPage.md │ ├── Frontend_Pages_GifMaker.md │ ├── Frontend_Pages_LandingPage.md │ ├── Frontend_Pages_LoadingPage.md │ ├── Frontend_Pages_LobbyLandingPage.md │ ├── Frontend_Pages_NotFoundPage.md │ ├── Frontend_Pages_ShareArtifact.md │ ├── Frontend_Pages_SharePlanet.md │ ├── Frontend_Pages_TestArtifactImages.md │ ├── Frontend_Pages_TxConfirmPopup.md │ ├── Frontend_Pages_UnsubscribePage.md │ ├── Frontend_Pages_ValhallaPage.md │ ├── Frontend_Panes_ArtifactCard.md │ ├── Frontend_Panes_ArtifactDetailsPane.md │ ├── Frontend_Panes_ArtifactHoverPane.md │ ├── Frontend_Panes_ArtifactsList.md │ ├── Frontend_Panes_BroadcastPane.md │ ├── Frontend_Panes_CoordsPane.md │ ├── Frontend_Panes_DiagnosticsPane.md │ ├── Frontend_Panes_ExplorePane.md │ ├── Frontend_Panes_HatPane.md │ ├── Frontend_Panes_HelpPane.md │ ├── Frontend_Panes_HoverPane.md │ ├── Frontend_Panes_HoverPlanetPane.md │ ├── Frontend_Panes_Lobbies_AdminPermissionsPane.md │ ├── Frontend_Panes_Lobbies_ArtifactSettingsPane.md │ ├── Frontend_Panes_Lobbies_CaptureZonesPane.md │ ├── Frontend_Panes_Lobbies_ConfigurationPane.md │ ├── Frontend_Panes_Lobbies_GameSettingsPane.md │ ├── Frontend_Panes_Lobbies_LobbiesUtils.md │ ├── Frontend_Panes_Lobbies_MinimapPane.md │ ├── Frontend_Panes_Lobbies_MinimapUtils.md │ ├── Frontend_Panes_Lobbies_PlanetPane.md │ ├── Frontend_Panes_Lobbies_PlayerSpawnPane.md │ ├── Frontend_Panes_Lobbies_Reducer.md │ ├── Frontend_Panes_Lobbies_SnarkPane.md │ ├── Frontend_Panes_Lobbies_SpaceJunkPane.md │ ├── Frontend_Panes_Lobbies_SpaceTypeBiomePane.md │ ├── Frontend_Panes_Lobbies_WorldSizePane.md │ ├── Frontend_Panes_ManagePlanetArtifacts_ArtifactActions.md │ ├── Frontend_Panes_ManagePlanetArtifacts_ManageArtifacts.md │ ├── Frontend_Panes_ManagePlanetArtifacts_ManagePlanetArtifactsPane.md │ ├── Frontend_Panes_ManagePlanetArtifacts_SortBy.md │ ├── Frontend_Panes_ManagePlanetArtifacts_UpgradeStatsView.md │ ├── Frontend_Panes_OnboardingPane.md │ ├── Frontend_Panes_PlanetContextPane.md │ ├── Frontend_Panes_PlanetDexPane.md │ ├── Frontend_Panes_PlanetInfoPane.md │ ├── Frontend_Panes_PlayerArtifactsPane.md │ ├── Frontend_Panes_PluginEditorPane.md │ ├── Frontend_Panes_PluginLibraryPane.md │ ├── Frontend_Panes_PrivatePane.md │ ├── Frontend_Panes_SettingsPane.md │ ├── Frontend_Panes_Tooltip.md │ ├── Frontend_Panes_TooltipPanes.md │ ├── Frontend_Panes_TransactionLogPane.md │ ├── Frontend_Panes_TutorialPane.md │ ├── Frontend_Panes_TwitterVerifyPane.md │ ├── Frontend_Panes_UpgradeDetailsPane.md │ ├── Frontend_Panes_WikiPane.md │ ├── Frontend_Panes_ZoomPane.md │ ├── Frontend_Renderers_Artifacts_ArtifactRenderer.md │ ├── Frontend_Renderers_GifRenderer.md │ ├── Frontend_Renderers_LandingPageCanvas.md │ ├── Frontend_Renderers_PlanetscapeRenderer_PlanetIcons.md │ ├── Frontend_Styles_Colors.md │ ├── Frontend_Styles_Mixins.md │ ├── Frontend_Styles_dfstyles.md │ ├── Frontend_Utils_AppHooks.md │ ├── Frontend_Utils_BrowserChecks.md │ ├── Frontend_Utils_EmitterHooks.md │ ├── Frontend_Utils_EmitterUtils.md │ ├── Frontend_Utils_Hooks.md │ ├── Frontend_Utils_KeyEmitters.md │ ├── Frontend_Utils_SettingsHooks.md │ ├── Frontend_Utils_ShortcutConstants.md │ ├── Frontend_Utils_TerminalTypes.md │ ├── Frontend_Utils_TimeUtils.md │ ├── Frontend_Utils_UIEmitter.md │ ├── Frontend_Utils_constants.md │ ├── Frontend_Utils_createDefinedContext.md │ ├── Frontend_Views_ArtifactLink.md │ ├── Frontend_Views_ArtifactRow.md │ ├── Frontend_Views_CadetWormhole.md │ ├── Frontend_Views_DFErrorBoundary.md │ ├── Frontend_Views_DarkForestTips.md │ ├── Frontend_Views_EmojiPicker.md │ ├── Frontend_Views_EmojiPlanetNotification.md │ ├── Frontend_Views_GameWindowLayout.md │ ├── Frontend_Views_GenericErrorBoundary.md │ ├── Frontend_Views_LandingPageRoundArt.md │ ├── Frontend_Views_Leaderboard.md │ ├── Frontend_Views_ModalIcon.md │ ├── Frontend_Views_ModalPane.md │ ├── Frontend_Views_NetworkHealth.md │ ├── Frontend_Views_Notifications.md │ ├── Frontend_Views_Paused.md │ ├── Frontend_Views_PlanetCard.md │ ├── Frontend_Views_PlanetCardComponents.md │ ├── Frontend_Views_PlanetLink.md │ ├── Frontend_Views_PlanetNotifications.md │ ├── Frontend_Views_SendResources.md │ ├── Frontend_Views_Share.md │ ├── Frontend_Views_SidebarPane.md │ ├── Frontend_Views_SortableTable.md │ ├── Frontend_Views_TabbedView.md │ ├── Frontend_Views_Table.md │ ├── Frontend_Views_Terminal.md │ ├── Frontend_Views_TopBar.md │ ├── Frontend_Views_UpgradePreview.md │ ├── Frontend_Views_WithdrawSilver.md │ ├── types_darkforest_api_ChunkStoreTypes.md │ ├── types_darkforest_api_ContractsAPITypes.md │ ├── types_darkforest_api_UtilityServerAPITypes.md │ ├── types_file_loader_FileWorkerTypes.__darkforest_eth_contracts_abis___json_.md │ ├── types_file_loader_FileWorkerTypes.__darkforest_eth_snarks___wasm_.md │ ├── types_file_loader_FileWorkerTypes.__darkforest_eth_snarks___zkey_.md │ ├── types_file_loader_FileWorkerTypes.md │ └── types_global_GlobalTypes.md ├── embedded_plugins/ │ ├── Admin-Controls.ts │ ├── Getting-Started.ts │ ├── Locate-Artifacts.ts │ ├── Rage-Cage.ts │ ├── Remote-Explorer.ts │ └── Renderer-Showcase.ts ├── index.html ├── last_updated.txt ├── netlify.toml ├── package.json ├── plugins/ │ ├── PluginTemplate.ts │ ├── README.md │ └── RendererPlugin.md ├── public/ │ ├── manifest.json │ └── robots.txt ├── src/ │ ├── Backend/ │ │ ├── GameLogic/ │ │ │ ├── ArrivalUtils.ts │ │ │ ├── CaptureZoneGenerator.ts │ │ │ ├── ContractsAPI.ts │ │ │ ├── GameManager.ts │ │ │ ├── GameObjects.ts │ │ │ ├── GameUIManager.ts │ │ │ ├── InitialGameStateDownloader.tsx │ │ │ ├── LayeredMap.ts │ │ │ ├── PluginManager.tsx │ │ │ ├── TutorialManager.ts │ │ │ └── ViewportEntities.ts │ │ ├── Miner/ │ │ │ ├── ChunkUtils.ts │ │ │ ├── MinerManager.ts │ │ │ ├── MiningPatterns.ts │ │ │ ├── miner.worker.ts │ │ │ └── permutation.ts │ │ ├── Network/ │ │ │ ├── AccountManager.ts │ │ │ ├── Blockchain.ts │ │ │ ├── EventLogger.ts │ │ │ ├── LeaderboardApi.ts │ │ │ ├── MessageAPI.ts │ │ │ ├── NetworkHealthApi.ts │ │ │ └── UtilityServerAPI.ts │ │ ├── Plugins/ │ │ │ ├── EmbeddedPluginLoader.ts │ │ │ ├── PluginProcess.ts │ │ │ ├── PluginTemplate.ts │ │ │ └── SerializedPlugin.ts │ │ ├── Storage/ │ │ │ ├── PersistentChunkStore.ts │ │ │ └── ReaderDataStore.ts │ │ └── Utils/ │ │ ├── Animation.ts │ │ ├── Coordinates.ts │ │ ├── SnarkArgsHelper.ts │ │ ├── Utils.ts │ │ ├── WhitelistSnarkArgsHelper.ts │ │ └── Wrapper.ts │ ├── Frontend/ │ │ ├── Components/ │ │ │ ├── AncientLabel.tsx │ │ │ ├── ArtifactImage.tsx │ │ │ ├── BiomeAnims.tsx │ │ │ ├── Btn.tsx │ │ │ ├── Button.tsx │ │ │ ├── CapturePlanetButton.tsx │ │ │ ├── CoreUI.tsx │ │ │ ├── Corner.tsx │ │ │ ├── DisplayGasPrices.tsx │ │ │ ├── Email.tsx │ │ │ ├── GameLandingPageComponents.tsx │ │ │ ├── GameWindowComponents.tsx │ │ │ ├── Icons.tsx │ │ │ ├── Input.tsx │ │ │ ├── Labels/ │ │ │ │ ├── ArtifactLabels.tsx │ │ │ │ ├── BiomeLabels.tsx │ │ │ │ ├── KeywordLabels.tsx │ │ │ │ ├── Labels.tsx │ │ │ │ ├── LavaLabel.tsx │ │ │ │ ├── LegendaryLabel.tsx │ │ │ │ ├── MythicLabel.tsx │ │ │ │ ├── PlanetLabels.tsx │ │ │ │ ├── SpacetimeRipLabel.tsx │ │ │ │ └── WastelandLabel.tsx │ │ │ ├── LoadingSpinner.tsx │ │ │ ├── MaybeShortcutButton.tsx │ │ │ ├── MineArtifactButton.tsx │ │ │ ├── Modal.tsx │ │ │ ├── OpenPaneButtons.tsx │ │ │ ├── PluginModal.tsx │ │ │ ├── ReadMore.tsx │ │ │ ├── RemoteModal.tsx │ │ │ ├── Row.tsx │ │ │ ├── Slider.tsx │ │ │ ├── Text.tsx │ │ │ ├── TextLoadingBar.tsx │ │ │ ├── TextPreview.tsx │ │ │ ├── Theme.tsx │ │ │ └── TimeUntil.tsx │ │ ├── EntryPoints/ │ │ │ └── index.tsx │ │ ├── Game/ │ │ │ ├── ControllableCanvas.tsx │ │ │ ├── ModalManager.ts │ │ │ ├── NotificationManager.tsx │ │ │ ├── Popups.ts │ │ │ └── Viewport.ts │ │ ├── Pages/ │ │ │ ├── App.tsx │ │ │ ├── CreateLobby.tsx │ │ │ ├── EventsPage.tsx │ │ │ ├── GameLandingPage.tsx │ │ │ ├── GifMaker.tsx │ │ │ ├── LandingPage.tsx │ │ │ ├── LoadingPage.tsx │ │ │ ├── LobbyLandingPage.tsx │ │ │ ├── NotFoundPage.tsx │ │ │ ├── ShareArtifact.tsx │ │ │ ├── SharePlanet.tsx │ │ │ ├── TestArtifactImages.tsx │ │ │ ├── TxConfirmPopup.tsx │ │ │ ├── UnsubscribePage.tsx │ │ │ └── ValhallaPage.tsx │ │ ├── Panes/ │ │ │ ├── ArtifactCard.tsx │ │ │ ├── ArtifactDetailsPane.tsx │ │ │ ├── ArtifactHoverPane.tsx │ │ │ ├── ArtifactsList.tsx │ │ │ ├── BroadcastPane.tsx │ │ │ ├── CoordsPane.tsx │ │ │ ├── DiagnosticsPane.tsx │ │ │ ├── ExplorePane.tsx │ │ │ ├── HatPane.tsx │ │ │ ├── HelpPane.tsx │ │ │ ├── HoverPane.tsx │ │ │ ├── HoverPlanetPane.tsx │ │ │ ├── Lobbies/ │ │ │ │ ├── AdminPermissionsPane.tsx │ │ │ │ ├── ArtifactSettingsPane.tsx │ │ │ │ ├── CaptureZonesPane.tsx │ │ │ │ ├── ConfigurationPane.tsx │ │ │ │ ├── GameSettingsPane.tsx │ │ │ │ ├── LobbiesUtils.tsx │ │ │ │ ├── MinimapPane.tsx │ │ │ │ ├── MinimapUtils.ts │ │ │ │ ├── PlanetPane.tsx │ │ │ │ ├── PlayerSpawnPane.tsx │ │ │ │ ├── Reducer.ts │ │ │ │ ├── SnarkPane.tsx │ │ │ │ ├── SpaceJunkPane.tsx │ │ │ │ ├── SpaceTypeBiomePane.tsx │ │ │ │ ├── WorldSizePane.tsx │ │ │ │ └── minimap.worker.ts │ │ │ ├── ManagePlanetArtifacts/ │ │ │ │ ├── ArtifactActions.tsx │ │ │ │ ├── ManageArtifacts.tsx │ │ │ │ ├── ManagePlanetArtifactsPane.tsx │ │ │ │ ├── SortBy.tsx │ │ │ │ └── UpgradeStatsView.tsx │ │ │ ├── OnboardingPane.tsx │ │ │ ├── PlanetContextPane.tsx │ │ │ ├── PlanetDexPane.tsx │ │ │ ├── PlanetInfoPane.tsx │ │ │ ├── PlayerArtifactsPane.tsx │ │ │ ├── PluginEditorPane.tsx │ │ │ ├── PluginLibraryPane.tsx │ │ │ ├── PrivatePane.tsx │ │ │ ├── SettingsPane.tsx │ │ │ ├── Tooltip.tsx │ │ │ ├── TooltipPanes.tsx │ │ │ ├── TransactionLogPane.tsx │ │ │ ├── TutorialPane.tsx │ │ │ ├── TwitterVerifyPane.tsx │ │ │ ├── UpgradeDetailsPane.tsx │ │ │ ├── WikiPane.tsx │ │ │ └── ZoomPane.tsx │ │ ├── Renderers/ │ │ │ ├── Artifacts/ │ │ │ │ └── ArtifactRenderer.ts │ │ │ ├── GifRenderer.ts │ │ │ ├── LandingPageCanvas.tsx │ │ │ └── PlanetscapeRenderer/ │ │ │ └── PlanetIcons.tsx │ │ ├── Styles/ │ │ │ ├── Colors.tsx │ │ │ ├── Mixins.tsx │ │ │ ├── dfstyles.ts │ │ │ ├── font/ │ │ │ │ ├── generator_config.txt │ │ │ │ ├── perfect_dos_vga_437-demo.html │ │ │ │ ├── specimen_files/ │ │ │ │ │ ├── grid_12-825-55-15.css │ │ │ │ │ └── specimen_stylesheet.css │ │ │ │ └── stylesheet.css │ │ │ ├── icomoon/ │ │ │ │ └── style.css │ │ │ ├── preflight.css │ │ │ └── style.css │ │ ├── Utils/ │ │ │ ├── AppHooks.ts │ │ │ ├── BrowserChecks.ts │ │ │ ├── EmitterHooks.ts │ │ │ ├── EmitterUtils.ts │ │ │ ├── Hooks.tsx │ │ │ ├── KeyEmitters.ts │ │ │ ├── SettingsHooks.tsx │ │ │ ├── ShortcutConstants.ts │ │ │ ├── TerminalTypes.ts │ │ │ ├── TimeUtils.ts │ │ │ ├── UIEmitter.ts │ │ │ ├── constants.ts │ │ │ └── createDefinedContext.ts │ │ └── Views/ │ │ ├── ArtifactLink.tsx │ │ ├── ArtifactRow.tsx │ │ ├── CadetWormhole.tsx │ │ ├── DFErrorBoundary.tsx │ │ ├── DarkForestTips.tsx │ │ ├── EmojiPicker.tsx │ │ ├── EmojiPlanetNotification.tsx │ │ ├── GameWindowLayout.tsx │ │ ├── GenericErrorBoundary.tsx │ │ ├── LandingPageRoundArt.tsx │ │ ├── Leaderboard.tsx │ │ ├── ModalIcon.tsx │ │ ├── ModalPane.tsx │ │ ├── NetworkHealth.tsx │ │ ├── Notifications.tsx │ │ ├── Paused.tsx │ │ ├── PlanetCard.tsx │ │ ├── PlanetCardComponents.tsx │ │ ├── PlanetLink.tsx │ │ ├── PlanetNotifications.tsx │ │ ├── SendResources.tsx │ │ ├── Share.tsx │ │ ├── SidebarPane.tsx │ │ ├── SortableTable.tsx │ │ ├── TabbedView.tsx │ │ ├── Table.tsx │ │ ├── Terminal.tsx │ │ ├── TopBar.tsx │ │ ├── UpgradePreview.tsx │ │ └── WithdrawSilver.tsx │ └── _types/ │ ├── darkforest/ │ │ └── api/ │ │ ├── ChunkStoreTypes.ts │ │ ├── ContractsAPITypes.ts │ │ └── UtilityServerAPITypes.ts │ ├── file-loader/ │ │ └── FileWorkerTypes.ts │ └── global/ │ ├── GlobalTypes.ts │ └── global.d.ts ├── tsconfig.decs.json ├── tsconfig.json ├── tsconfig.ref.json └── webpack.config.js ================================================ FILE CONTENTS ================================================ ================================================ FILE: .babelrc ================================================ { "presets": [ "@babel/preset-env", "@babel/preset-react", [ "@babel/preset-typescript", { "optimizeConstEnums": true } ] ], "plugins": ["babel-plugin-styled-components"] } ================================================ FILE: .dockerignore ================================================ node_modules ================================================ FILE: .eslintignore ================================================ public/ declarations/ dist/ plugins/ ================================================ FILE: .gitignore ================================================ # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. # dependencies /node_modules # production /dist # misc .DS_Store npm-debug.log* yarn-debug.log* yarn-error.log* # firebase .firebase # local image files for testing public/img/artifacts declarations tsconfig.ref.tsbuildinfo # Local Netlify folder .netlify # Environment variables .env ================================================ FILE: .nvmrc ================================================ 16 ================================================ FILE: .prettierignore ================================================ dist/ declarations/ plugins/dist/ embedded_plugins/dist/ public/ src/styles/font/ src/styles/icomoon/ embedded_plugins/Remote-Explorer.ts embedded_plugins/Renderer-Showcase.ts ================================================ FILE: LICENSE ================================================ GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The GNU General Public License is a free, copyleft license for software and other kinds of works. The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free software for all its users. We, the Free Software Foundation, use the GNU General Public License for most of our software; it applies also to any other work released this way by its authors. You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for them if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs, and that you know you can do these things. To protect your rights, we need to prevent others from denying you these rights or asking you to surrender the rights. Therefore, you have certain responsibilities if you distribute copies of the software, or if you modify it: responsibilities to respect the freedom of others. For example, if you distribute copies of such a program, whether gratis or for a fee, you must pass on to the recipients the same freedoms that you received. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. Developers that use the GNU GPL protect your rights with two steps: (1) assert copyright on the software, and (2) offer you this License giving you legal permission to copy, distribute and/or modify it. For the developers' and authors' protection, the GPL clearly explains that there is no warranty for this free software. For both users' and authors' sake, the GPL requires that modified versions be marked as changed, so that their problems will not be attributed erroneously to authors of previous versions. Some devices are designed to deny users access to install or run modified versions of the software inside them, although the manufacturer can do so. This is fundamentally incompatible with the aim of protecting users' freedom to change the software. The systematic pattern of such abuse occurs in the area of products for individuals to use, which is precisely where it is most unacceptable. Therefore, we have designed this version of the GPL to prohibit the practice for those products. If such problems arise substantially in other domains, we stand ready to extend this provision to those domains in future versions of the GPL, as needed to protect the freedom of users. Finally, every program is threatened constantly by software patents. States should not allow patents to restrict development and use of software on general-purpose computers, but in those that do, we wish to avoid the special danger that patents applied to a free program could make it effectively proprietary. To prevent this, the GPL assures that patents cannot be used to render the program non-free. The precise terms and conditions for copying, distribution and modification follow. TERMS AND CONDITIONS 0. Definitions. "This License" refers to version 3 of the GNU General Public License. "Copyright" also means copyright-like laws that apply to other kinds of works, such as semiconductor masks. "The Program" refers to any copyrightable work licensed under this License. Each licensee is addressed as "you". "Licensees" and "recipients" may be individuals or organizations. To "modify" a work means to copy from or adapt all or part of the work in a fashion requiring copyright permission, other than the making of an exact copy. The resulting work is called a "modified version" of the earlier work or a work "based on" the earlier work. A "covered work" means either the unmodified Program or a work based on the Program. To "propagate" a work means to do anything with it that, without permission, would make you directly or secondarily liable for infringement under applicable copyright law, except executing it on a computer or modifying a private copy. Propagation includes copying, distribution (with or without modification), making available to the public, and in some countries other activities as well. To "convey" a work means any kind of propagation that enables other parties to make or receive copies. Mere interaction with a user through a computer network, with no transfer of a copy, is not conveying. An interactive user interface displays "Appropriate Legal Notices" to the extent that it includes a convenient and prominently visible feature that (1) displays an appropriate copyright notice, and (2) tells the user that there is no warranty for the work (except to the extent that warranties are provided), that licensees may convey the work under this License, and how to view a copy of this License. If the interface presents a list of user commands or options, such as a menu, a prominent item in the list meets this criterion. 1. Source Code. The "source code" for a work means the preferred form of the work for making modifications to it. "Object code" means any non-source form of a work. A "Standard Interface" means an interface that either is an official standard defined by a recognized standards body, or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language. The "System Libraries" of an executable work include anything, other than the work as a whole, that (a) is included in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component, or to implement a Standard Interface for which an implementation is available to the public in source code form. A "Major Component", in this context, means a major essential component (kernel, window system, and so on) of the specific operating system (if any) on which the executable work runs, or a compiler used to produce the work, or an object code interpreter used to run it. The "Corresponding Source" for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to control those activities. However, it does not include the work's System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but which are not part of the work. For example, Corresponding Source includes interface definition files associated with source files for the work, and the source code for shared libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data communication or control flow between those subprograms and other parts of the work. The Corresponding Source need not include anything that users can regenerate automatically from other parts of the Corresponding Source. The Corresponding Source for a work in source code form is that same work. 2. Basic Permissions. All rights granted under this License are granted for the term of copyright on the Program, and are irrevocable provided the stated conditions are met. This License explicitly affirms your unlimited permission to run the unmodified Program. The output from running a covered work is covered by this License only if the output, given its content, constitutes a covered work. This License acknowledges your rights of fair use or other equivalent, as provided by copyright law. You may make, run and propagate covered works that you do not convey, without conditions so long as your license otherwise remains in force. You may convey covered works to others for the sole purpose of having them make modifications exclusively for you, or provide you with facilities for running those works, provided that you comply with the terms of this License in conveying all material for which you do not control copyright. Those thus making or running the covered works for you must do so exclusively on your behalf, under your direction and control, on terms that prohibit them from making any copies of your copyrighted material outside their relationship with you. Conveying under any other circumstances is permitted solely under the conditions stated below. Sublicensing is not allowed; section 10 makes it unnecessary. 3. Protecting Users' Legal Rights From Anti-Circumvention Law. No covered work shall be deemed part of an effective technological measure under any applicable law fulfilling obligations under article 11 of the WIPO copyright treaty adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention of such measures. When you convey a covered work, you waive any legal power to forbid circumvention of technological measures to the extent such circumvention is effected by exercising rights under this License with respect to the covered work, and you disclaim any intention to limit operation or modification of the work as a means of enforcing, against the work's users, your or third parties' legal rights to forbid circumvention of technological measures. 4. Conveying Verbatim Copies. You may convey verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice; keep intact all notices stating that this License and any non-permissive terms added in accord with section 7 apply to the code; keep intact all notices of the absence of any warranty; and give all recipients a copy of this License along with the Program. You may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for a fee. 5. Conveying Modified Source Versions. You may convey a work based on the Program, or the modifications to produce it from the Program, in the form of source code under the terms of section 4, provided that you also meet all of these conditions: a) The work must carry prominent notices stating that you modified it, and giving a relevant date. b) The work must carry prominent notices stating that it is released under this License and any conditions added under section 7. This requirement modifies the requirement in section 4 to "keep intact all notices". c) You must license the entire work, as a whole, under this License to anyone who comes into possession of a copy. This License will therefore apply, along with any applicable section 7 additional terms, to the whole of the work, and all its parts, regardless of how they are packaged. This License gives no permission to license the work in any other way, but it does not invalidate such permission if you have separately received it. d) If the work has interactive user interfaces, each must display Appropriate Legal Notices; however, if the Program has interactive interfaces that do not display Appropriate Legal Notices, your work need not make them do so. A compilation of a covered work with other separate and independent works, which are not by their nature extensions of the covered work, and which are not combined with it such as to form a larger program, in or on a volume of a storage or distribution medium, is called an "aggregate" if the compilation and its resulting copyright are not used to limit the access or legal rights of the compilation's users beyond what the individual works permit. Inclusion of a covered work in an aggregate does not cause this License to apply to the other parts of the aggregate. 6. Conveying Non-Source Forms. You may convey a covered work in object code form under the terms of sections 4 and 5, provided that you also convey the machine-readable Corresponding Source under the terms of this License, in one of these ways: a) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by the Corresponding Source fixed on a durable physical medium customarily used for software interchange. b) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by a written offer, valid for at least three years and valid for as long as you offer spare parts or customer support for that product model, to give anyone who possesses the object code either (1) a copy of the Corresponding Source for all the software in the product that is covered by this License, on a durable physical medium customarily used for software interchange, for a price no more than your reasonable cost of physically performing this conveying of source, or (2) access to copy the Corresponding Source from a network server at no charge. c) Convey individual copies of the object code with a copy of the written offer to provide the Corresponding Source. This alternative is allowed only occasionally and noncommercially, and only if you received the object code with such an offer, in accord with subsection 6b. d) Convey the object code by offering access from a designated place (gratis or for a charge), and offer equivalent access to the Corresponding Source in the same way through the same place at no further charge. You need not require recipients to copy the Corresponding Source along with the object code. If the place to copy the object code is a network server, the Corresponding Source may be on a different server (operated by you or a third party) that supports equivalent copying facilities, provided you maintain clear directions next to the object code saying where to find the Corresponding Source. Regardless of what server hosts the Corresponding Source, you remain obligated to ensure that it is available for as long as needed to satisfy these requirements. e) Convey the object code using peer-to-peer transmission, provided you inform other peers where the object code and Corresponding Source of the work are being offered to the general public at no charge under subsection 6d. A separable portion of the object code, whose source code is excluded from the Corresponding Source as a System Library, need not be included in conveying the object code work. A "User Product" is either (1) a "consumer product", which means any tangible personal property which is normally used for personal, family, or household purposes, or (2) anything designed or sold for incorporation into a dwelling. In determining whether a product is a consumer product, doubtful cases shall be resolved in favor of coverage. For a particular product received by a particular user, "normally used" refers to a typical or common use of that class of product, regardless of the status of the particular user or of the way in which the particular user actually uses, or expects or is expected to use, the product. A product is a consumer product regardless of whether the product has substantial commercial, industrial or non-consumer uses, unless such uses represent the only significant mode of use of the product. "Installation Information" for a User Product means any methods, procedures, authorization keys, or other information required to install and execute modified versions of a covered work in that User Product from a modified version of its Corresponding Source. The information must suffice to ensure that the continued functioning of the modified object code is in no case prevented or interfered with solely because modification has been made. If you convey an object code work under this section in, or with, or specifically for use in, a User Product, and the conveying occurs as part of a transaction in which the right of possession and use of the User Product is transferred to the recipient in perpetuity or for a fixed term (regardless of how the transaction is characterized), the Corresponding Source conveyed under this section must be accompanied by the Installation Information. But this requirement does not apply if neither you nor any third party retains the ability to install modified object code on the User Product (for example, the work has been installed in ROM). The requirement to provide Installation Information does not include a requirement to continue to provide support service, warranty, or updates for a work that has been modified or installed by the recipient, or for the User Product in which it has been modified or installed. Access to a network may be denied when the modification itself materially and adversely affects the operation of the network or violates the rules and protocols for communication across the network. Corresponding Source conveyed, and Installation Information provided, in accord with this section must be in a format that is publicly documented (and with an implementation available to the public in source code form), and must require no special password or key for unpacking, reading or copying. 7. Additional Terms. "Additional permissions" are terms that supplement the terms of this License by making exceptions from one or more of its conditions. Additional permissions that are applicable to the entire Program shall be treated as though they were included in this License, to the extent that they are valid under applicable law. If additional permissions apply only to part of the Program, that part may be used separately under those permissions, but the entire Program remains governed by this License without regard to the additional permissions. When you convey a copy of a covered work, you may at your option remove any additional permissions from that copy, or from any part of it. (Additional permissions may be written to require their own removal in certain cases when you modify the work.) You may place additional permissions on material, added by you to a covered work, for which you have or can give appropriate copyright permission. Notwithstanding any other provision of this License, for material you add to a covered work, you may (if authorized by the copyright holders of that material) supplement the terms of this License with terms: a) Disclaiming warranty or limiting liability differently from the terms of sections 15 and 16 of this License; or b) Requiring preservation of specified reasonable legal notices or author attributions in that material or in the Appropriate Legal Notices displayed by works containing it; or c) Prohibiting misrepresentation of the origin of that material, or requiring that modified versions of such material be marked in reasonable ways as different from the original version; or d) Limiting the use for publicity purposes of names of licensors or authors of the material; or e) Declining to grant rights under trademark law for use of some trade names, trademarks, or service marks; or f) Requiring indemnification of licensors and authors of that material by anyone who conveys the material (or modified versions of it) with contractual assumptions of liability to the recipient, for any liability that these contractual assumptions directly impose on those licensors and authors. All other non-permissive additional terms are considered "further restrictions" within the meaning of section 10. If the Program as you received it, or any part of it, contains a notice stating that it is governed by this License along with a term that is a further restriction, you may remove that term. If a license document contains a further restriction but permits relicensing or conveying under this License, you may add to a covered work material governed by the terms of that license document, provided that the further restriction does not survive such relicensing or conveying. If you add terms to a covered work in accord with this section, you must place, in the relevant source files, a statement of the additional terms that apply to those files, or a notice indicating where to find the applicable terms. Additional terms, permissive or non-permissive, may be stated in the form of a separately written license, or stated as exceptions; the above requirements apply either way. 8. Termination. You may not propagate or modify a covered work except as expressly provided under this License. Any attempt otherwise to propagate or modify it is void, and will automatically terminate your rights under this License (including any patent licenses granted under the third paragraph of section 11). However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation. Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice. Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, you do not qualify to receive new licenses for the same material under section 10. 9. Acceptance Not Required for Having Copies. You are not required to accept this License in order to receive or run a copy of the Program. Ancillary propagation of a covered work occurring solely as a consequence of using peer-to-peer transmission to receive a copy likewise does not require acceptance. However, nothing other than this License grants you permission to propagate or modify any covered work. These actions infringe copyright if you do not accept this License. Therefore, by modifying or propagating a covered work, you indicate your acceptance of this License to do so. 10. Automatic Licensing of Downstream Recipients. Each time you convey a covered work, the recipient automatically receives a license from the original licensors, to run, modify and propagate that work, subject to this License. You are not responsible for enforcing compliance by third parties with this License. An "entity transaction" is a transaction transferring control of an organization, or substantially all assets of one, or subdividing an organization, or merging organizations. If propagation of a covered work results from an entity transaction, each party to that transaction who receives a copy of the work also receives whatever licenses to the work the party's predecessor in interest had or could give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with reasonable efforts. You may not impose any further restrictions on the exercise of the rights granted or affirmed under this License. For example, you may not impose a license fee, royalty, or other charge for exercise of rights granted under this License, and you may not initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using, selling, offering for sale, or importing the Program or any portion of it. 11. Patents. A "contributor" is a copyright holder who authorizes use under this License of the Program or a work on which the Program is based. The work thus licensed is called the contributor's "contributor version". A contributor's "essential patent claims" are all patent claims owned or controlled by the contributor, whether already acquired or hereafter acquired, that would be infringed by some manner, permitted by this License, of making, using, or selling its contributor version, but do not include claims that would be infringed only as a consequence of further modification of the contributor version. For purposes of this definition, "control" includes the right to grant patent sublicenses in a manner consistent with the requirements of this License. Each contributor grants you a non-exclusive, worldwide, royalty-free patent license under the contributor's essential patent claims, to make, use, sell, offer for sale, import and otherwise run, modify and propagate the contents of its contributor version. In the following three paragraphs, a "patent license" is any express agreement or commitment, however denominated, not to enforce a patent (such as an express permission to practice a patent or covenant not to sue for patent infringement). To "grant" such a patent license to a party means to make such an agreement or commitment not to enforce a patent against the party. If you convey a covered work, knowingly relying on a patent license, and the Corresponding Source of the work is not available for anyone to copy, free of charge and under the terms of this License, through a publicly available network server or other readily accessible means, then you must either (1) cause the Corresponding Source to be so available, or (2) arrange to deprive yourself of the benefit of the patent license for this particular work, or (3) arrange, in a manner consistent with the requirements of this License, to extend the patent license to downstream recipients. "Knowingly relying" means you have actual knowledge that, but for the patent license, your conveying the covered work in a country, or your recipient's use of the covered work in a country, would infringe one or more identifiable patents in that country that you have reason to believe are valid. If, pursuant to or in connection with a single transaction or arrangement, you convey, or propagate by procuring conveyance of, a covered work, and grant a patent license to some of the parties receiving the covered work authorizing them to use, propagate, modify or convey a specific copy of the covered work, then the patent license you grant is automatically extended to all recipients of the covered work and works based on it. A patent license is "discriminatory" if it does not include within the scope of its coverage, prohibits the exercise of, or is conditioned on the non-exercise of one or more of the rights that are specifically granted under this License. You may not convey a covered work if you are a party to an arrangement with a third party that is in the business of distributing software, under which you make payment to the third party based on the extent of your activity of conveying the work, and under which the third party grants, to any of the parties who would receive the covered work from you, a discriminatory patent license (a) in connection with copies of the covered work conveyed by you (or copies made from those copies), or (b) primarily for and in connection with specific products or compilations that contain the covered work, unless you entered into that arrangement, or that patent license was granted, prior to 28 March 2007. Nothing in this License shall be construed as excluding or limiting any implied license or other defenses to infringement that may otherwise be available to you under applicable patent law. 12. No Surrender of Others' Freedom. If conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot convey a covered work so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not convey it at all. For example, if you agree to terms that obligate you to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program. 13. Use with the GNU Affero General Public License. Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed under version 3 of the GNU Affero General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, but the special requirements of the GNU Affero General Public License, section 13, concerning interaction through a network will apply to the combination as such. 14. Revised Versions of this License. The Free Software Foundation may publish revised and/or new versions of the GNU General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies that a certain numbered version of the GNU General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the GNU General Public License, you may choose any version ever published by the Free Software Foundation. If the Program specifies that a proxy can decide which future versions of the GNU General Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program. Later license versions may give you additional or different permissions. However, no additional obligations are imposed on any author or copyright holder as a result of your choosing to follow a later version. 15. Disclaimer of Warranty. THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. Limitation of Liability. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 17. Interpretation of Sections 15 and 16. If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . Also add information on how to contact you by electronic and paper mail. If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode: Copyright (C) This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, your program's commands might be different; for a GUI interface, you would use an "about box". You should also get your employer (if you work as a programmer) or school, if any, to sign a "copyright disclaimer" for the program, if necessary. For more information on this, and how to apply and follow the GNU GPL, see . The GNU General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. But first, please read . ================================================ FILE: README.md ================================================ # Dark Forest Client ## Development Guide ### Installing Core Dependencies - Node (v14.x OR v16.x) - Yarn (Javascript Package Manager) #### Installing The Correct Node Version Using NVM Dark Forest is built and tested using Node.js v14/v16 and might not run properly on other Node.js versions. We recommend using NVM to switch between multiple Node.js version on your machine. ```sh curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.35.3/install.sh | bash nvm install ``` After the installation is finished, you can run `node --version` to verify that you are running v14 or v16 #### Installing Yarn Refer to [Yarn's official documentation](https://classic.yarnpkg.com/en/docs/install) for the installation guide. After you have Yarn installed, run `yarn` to install the dependencies: ### Running the client To connecting to the mainnet client, simply run `yarn start:prod`. When asked you can use your whitelist key or import your mainnet burner secret and home coordinates. ### Plugin development You can develop plugins for Dark Forest either inside this game client repository, or externally using something like https://github.com/Bind/my-first-plugin. In either case, you'll want to use the [`df-plugin-dev-server`](https://github.com/projectsophon/df-plugin-dev-server). You can install it as a global command, using: ```sh npm install -g @projectsophon/df-plugin-dev-server ``` Once it is installed, you can run it inside this project repository, using: ```sh df-plugin-dev-server ``` You can then add or modify any plugins inside the [`plugins/`](./plugins) directory and they will be automatically bundled and served as plugins you can import inside the game! And then load your plugin in the game client, like so: ```js // Replace PluginTemplate.js with the name of your Plugin // And `.ts` extensions become `.js` export { default } from 'http://127.0.0.1:2222/PluginTemplate.js?dev'; ``` ### Embedded plugins The Dark Forest client ships with some game "plugins" embedded in the game client. The source code for these plugins exists at [`embedded_plugins/`](./embedded_plugins). You are able to edit them inside the game and the changes will persist. If you change the source code directly, you must delete the plugin in-game and reload your browser to import the new code. ================================================ FILE: docs/README.md ================================================ # client ## Table of contents ### Modules - [Backend/GameLogic/ArrivalUtils](modules/Backend_GameLogic_ArrivalUtils.md) - [Backend/GameLogic/CaptureZoneGenerator](modules/Backend_GameLogic_CaptureZoneGenerator.md) - [Backend/GameLogic/ContractsAPI](modules/Backend_GameLogic_ContractsAPI.md) - [Backend/GameLogic/GameManager](modules/Backend_GameLogic_GameManager.md) - [Backend/GameLogic/GameObjects](modules/Backend_GameLogic_GameObjects.md) - [Backend/GameLogic/GameUIManager](modules/Backend_GameLogic_GameUIManager.md) - [Backend/GameLogic/InitialGameStateDownloader](modules/Backend_GameLogic_InitialGameStateDownloader.md) - [Backend/GameLogic/LayeredMap](modules/Backend_GameLogic_LayeredMap.md) - [Backend/GameLogic/PluginManager](modules/Backend_GameLogic_PluginManager.md) - [Backend/GameLogic/TutorialManager](modules/Backend_GameLogic_TutorialManager.md) - [Backend/GameLogic/ViewportEntities](modules/Backend_GameLogic_ViewportEntities.md) - [Backend/Miner/ChunkUtils](modules/Backend_Miner_ChunkUtils.md) - [Backend/Miner/MinerManager](modules/Backend_Miner_MinerManager.md) - [Backend/Miner/MiningPatterns](modules/Backend_Miner_MiningPatterns.md) - [Backend/Miner/permutation](modules/Backend_Miner_permutation.md) - [Backend/Network/AccountManager](modules/Backend_Network_AccountManager.md) - [Backend/Network/Blockchain](modules/Backend_Network_Blockchain.md) - [Backend/Network/EventLogger](modules/Backend_Network_EventLogger.md) - [Backend/Network/LeaderboardApi](modules/Backend_Network_LeaderboardApi.md) - [Backend/Network/MessageAPI](modules/Backend_Network_MessageAPI.md) - [Backend/Network/NetworkHealthApi](modules/Backend_Network_NetworkHealthApi.md) - [Backend/Network/UtilityServerAPI](modules/Backend_Network_UtilityServerAPI.md) - [Backend/Plugins/EmbeddedPluginLoader](modules/Backend_Plugins_EmbeddedPluginLoader.md) - [Backend/Plugins/PluginProcess](modules/Backend_Plugins_PluginProcess.md) - [Backend/Plugins/PluginTemplate](modules/Backend_Plugins_PluginTemplate.md) - [Backend/Plugins/SerializedPlugin](modules/Backend_Plugins_SerializedPlugin.md) - [Backend/Storage/PersistentChunkStore](modules/Backend_Storage_PersistentChunkStore.md) - [Backend/Storage/ReaderDataStore](modules/Backend_Storage_ReaderDataStore.md) - [Backend/Utils/Animation](modules/Backend_Utils_Animation.md) - [Backend/Utils/Coordinates](modules/Backend_Utils_Coordinates.md) - [Backend/Utils/SnarkArgsHelper](modules/Backend_Utils_SnarkArgsHelper.md) - [Backend/Utils/Utils](modules/Backend_Utils_Utils.md) - [Backend/Utils/WhitelistSnarkArgsHelper](modules/Backend_Utils_WhitelistSnarkArgsHelper.md) - [Backend/Utils/Wrapper](modules/Backend_Utils_Wrapper.md) - [Frontend/Components/AncientLabel](modules/Frontend_Components_AncientLabel.md) - [Frontend/Components/ArtifactImage](modules/Frontend_Components_ArtifactImage.md) - [Frontend/Components/BiomeAnims](modules/Frontend_Components_BiomeAnims.md) - [Frontend/Components/Btn](modules/Frontend_Components_Btn.md) - [Frontend/Components/Button](modules/Frontend_Components_Button.md) - [Frontend/Components/CapturePlanetButton](modules/Frontend_Components_CapturePlanetButton.md) - [Frontend/Components/CoreUI](modules/Frontend_Components_CoreUI.md) - [Frontend/Components/Corner](modules/Frontend_Components_Corner.md) - [Frontend/Components/DisplayGasPrices](modules/Frontend_Components_DisplayGasPrices.md) - [Frontend/Components/Email](modules/Frontend_Components_Email.md) - [Frontend/Components/GameLandingPageComponents](modules/Frontend_Components_GameLandingPageComponents.md) - [Frontend/Components/GameWindowComponents](modules/Frontend_Components_GameWindowComponents.md) - [Frontend/Components/Icons](modules/Frontend_Components_Icons.md) - [Frontend/Components/Input](modules/Frontend_Components_Input.md) - [Frontend/Components/Labels/ArtifactLabels](modules/Frontend_Components_Labels_ArtifactLabels.md) - [Frontend/Components/Labels/BiomeLabels](modules/Frontend_Components_Labels_BiomeLabels.md) - [Frontend/Components/Labels/KeywordLabels](modules/Frontend_Components_Labels_KeywordLabels.md) - [Frontend/Components/Labels/Labels](modules/Frontend_Components_Labels_Labels.md) - [Frontend/Components/Labels/LavaLabel](modules/Frontend_Components_Labels_LavaLabel.md) - [Frontend/Components/Labels/LegendaryLabel](modules/Frontend_Components_Labels_LegendaryLabel.md) - [Frontend/Components/Labels/MythicLabel](modules/Frontend_Components_Labels_MythicLabel.md) - [Frontend/Components/Labels/PlanetLabels](modules/Frontend_Components_Labels_PlanetLabels.md) - [Frontend/Components/Labels/SpacetimeRipLabel](modules/Frontend_Components_Labels_SpacetimeRipLabel.md) - [Frontend/Components/Labels/WastelandLabel](modules/Frontend_Components_Labels_WastelandLabel.md) - [Frontend/Components/LoadingSpinner](modules/Frontend_Components_LoadingSpinner.md) - [Frontend/Components/MaybeShortcutButton](modules/Frontend_Components_MaybeShortcutButton.md) - [Frontend/Components/MineArtifactButton](modules/Frontend_Components_MineArtifactButton.md) - [Frontend/Components/Modal](modules/Frontend_Components_Modal.md) - [Frontend/Components/OpenPaneButtons](modules/Frontend_Components_OpenPaneButtons.md) - [Frontend/Components/PluginModal](modules/Frontend_Components_PluginModal.md) - [Frontend/Components/ReadMore](modules/Frontend_Components_ReadMore.md) - [Frontend/Components/RemoteModal](modules/Frontend_Components_RemoteModal.md) - [Frontend/Components/Row](modules/Frontend_Components_Row.md) - [Frontend/Components/Slider](modules/Frontend_Components_Slider.md) - [Frontend/Components/Text](modules/Frontend_Components_Text.md) - [Frontend/Components/TextLoadingBar](modules/Frontend_Components_TextLoadingBar.md) - [Frontend/Components/TextPreview](modules/Frontend_Components_TextPreview.md) - [Frontend/Components/Theme](modules/Frontend_Components_Theme.md) - [Frontend/Components/TimeUntil](modules/Frontend_Components_TimeUntil.md) - [Frontend/Game/ControllableCanvas](modules/Frontend_Game_ControllableCanvas.md) - [Frontend/Game/ModalManager](modules/Frontend_Game_ModalManager.md) - [Frontend/Game/NotificationManager](modules/Frontend_Game_NotificationManager.md) - [Frontend/Game/Popups](modules/Frontend_Game_Popups.md) - [Frontend/Game/Viewport](modules/Frontend_Game_Viewport.md) - [Frontend/Pages/App](modules/Frontend_Pages_App.md) - [Frontend/Pages/CreateLobby](modules/Frontend_Pages_CreateLobby.md) - [Frontend/Pages/EventsPage](modules/Frontend_Pages_EventsPage.md) - [Frontend/Pages/GameLandingPage](modules/Frontend_Pages_GameLandingPage.md) - [Frontend/Pages/GifMaker](modules/Frontend_Pages_GifMaker.md) - [Frontend/Pages/LandingPage](modules/Frontend_Pages_LandingPage.md) - [Frontend/Pages/LoadingPage](modules/Frontend_Pages_LoadingPage.md) - [Frontend/Pages/LobbyLandingPage](modules/Frontend_Pages_LobbyLandingPage.md) - [Frontend/Pages/NotFoundPage](modules/Frontend_Pages_NotFoundPage.md) - [Frontend/Pages/ShareArtifact](modules/Frontend_Pages_ShareArtifact.md) - [Frontend/Pages/SharePlanet](modules/Frontend_Pages_SharePlanet.md) - [Frontend/Pages/TestArtifactImages](modules/Frontend_Pages_TestArtifactImages.md) - [Frontend/Pages/TxConfirmPopup](modules/Frontend_Pages_TxConfirmPopup.md) - [Frontend/Pages/UnsubscribePage](modules/Frontend_Pages_UnsubscribePage.md) - [Frontend/Pages/ValhallaPage](modules/Frontend_Pages_ValhallaPage.md) - [Frontend/Panes/ArtifactCard](modules/Frontend_Panes_ArtifactCard.md) - [Frontend/Panes/ArtifactDetailsPane](modules/Frontend_Panes_ArtifactDetailsPane.md) - [Frontend/Panes/ArtifactHoverPane](modules/Frontend_Panes_ArtifactHoverPane.md) - [Frontend/Panes/ArtifactsList](modules/Frontend_Panes_ArtifactsList.md) - [Frontend/Panes/BroadcastPane](modules/Frontend_Panes_BroadcastPane.md) - [Frontend/Panes/CoordsPane](modules/Frontend_Panes_CoordsPane.md) - [Frontend/Panes/DiagnosticsPane](modules/Frontend_Panes_DiagnosticsPane.md) - [Frontend/Panes/ExplorePane](modules/Frontend_Panes_ExplorePane.md) - [Frontend/Panes/HatPane](modules/Frontend_Panes_HatPane.md) - [Frontend/Panes/HelpPane](modules/Frontend_Panes_HelpPane.md) - [Frontend/Panes/HoverPane](modules/Frontend_Panes_HoverPane.md) - [Frontend/Panes/HoverPlanetPane](modules/Frontend_Panes_HoverPlanetPane.md) - [Frontend/Panes/Lobbies/AdminPermissionsPane](modules/Frontend_Panes_Lobbies_AdminPermissionsPane.md) - [Frontend/Panes/Lobbies/ArtifactSettingsPane](modules/Frontend_Panes_Lobbies_ArtifactSettingsPane.md) - [Frontend/Panes/Lobbies/CaptureZonesPane](modules/Frontend_Panes_Lobbies_CaptureZonesPane.md) - [Frontend/Panes/Lobbies/ConfigurationPane](modules/Frontend_Panes_Lobbies_ConfigurationPane.md) - [Frontend/Panes/Lobbies/GameSettingsPane](modules/Frontend_Panes_Lobbies_GameSettingsPane.md) - [Frontend/Panes/Lobbies/LobbiesUtils](modules/Frontend_Panes_Lobbies_LobbiesUtils.md) - [Frontend/Panes/Lobbies/MinimapPane](modules/Frontend_Panes_Lobbies_MinimapPane.md) - [Frontend/Panes/Lobbies/MinimapUtils](modules/Frontend_Panes_Lobbies_MinimapUtils.md) - [Frontend/Panes/Lobbies/PlanetPane](modules/Frontend_Panes_Lobbies_PlanetPane.md) - [Frontend/Panes/Lobbies/PlayerSpawnPane](modules/Frontend_Panes_Lobbies_PlayerSpawnPane.md) - [Frontend/Panes/Lobbies/Reducer](modules/Frontend_Panes_Lobbies_Reducer.md) - [Frontend/Panes/Lobbies/SnarkPane](modules/Frontend_Panes_Lobbies_SnarkPane.md) - [Frontend/Panes/Lobbies/SpaceJunkPane](modules/Frontend_Panes_Lobbies_SpaceJunkPane.md) - [Frontend/Panes/Lobbies/SpaceTypeBiomePane](modules/Frontend_Panes_Lobbies_SpaceTypeBiomePane.md) - [Frontend/Panes/Lobbies/WorldSizePane](modules/Frontend_Panes_Lobbies_WorldSizePane.md) - [Frontend/Panes/ManagePlanetArtifacts/ArtifactActions](modules/Frontend_Panes_ManagePlanetArtifacts_ArtifactActions.md) - [Frontend/Panes/ManagePlanetArtifacts/ManageArtifacts](modules/Frontend_Panes_ManagePlanetArtifacts_ManageArtifacts.md) - [Frontend/Panes/ManagePlanetArtifacts/ManagePlanetArtifactsPane](modules/Frontend_Panes_ManagePlanetArtifacts_ManagePlanetArtifactsPane.md) - [Frontend/Panes/ManagePlanetArtifacts/SortBy](modules/Frontend_Panes_ManagePlanetArtifacts_SortBy.md) - [Frontend/Panes/ManagePlanetArtifacts/UpgradeStatsView](modules/Frontend_Panes_ManagePlanetArtifacts_UpgradeStatsView.md) - [Frontend/Panes/OnboardingPane](modules/Frontend_Panes_OnboardingPane.md) - [Frontend/Panes/PlanetContextPane](modules/Frontend_Panes_PlanetContextPane.md) - [Frontend/Panes/PlanetDexPane](modules/Frontend_Panes_PlanetDexPane.md) - [Frontend/Panes/PlanetInfoPane](modules/Frontend_Panes_PlanetInfoPane.md) - [Frontend/Panes/PlayerArtifactsPane](modules/Frontend_Panes_PlayerArtifactsPane.md) - [Frontend/Panes/PluginEditorPane](modules/Frontend_Panes_PluginEditorPane.md) - [Frontend/Panes/PluginLibraryPane](modules/Frontend_Panes_PluginLibraryPane.md) - [Frontend/Panes/PrivatePane](modules/Frontend_Panes_PrivatePane.md) - [Frontend/Panes/SettingsPane](modules/Frontend_Panes_SettingsPane.md) - [Frontend/Panes/Tooltip](modules/Frontend_Panes_Tooltip.md) - [Frontend/Panes/TooltipPanes](modules/Frontend_Panes_TooltipPanes.md) - [Frontend/Panes/TransactionLogPane](modules/Frontend_Panes_TransactionLogPane.md) - [Frontend/Panes/TutorialPane](modules/Frontend_Panes_TutorialPane.md) - [Frontend/Panes/TwitterVerifyPane](modules/Frontend_Panes_TwitterVerifyPane.md) - [Frontend/Panes/UpgradeDetailsPane](modules/Frontend_Panes_UpgradeDetailsPane.md) - [Frontend/Panes/WikiPane](modules/Frontend_Panes_WikiPane.md) - [Frontend/Panes/ZoomPane](modules/Frontend_Panes_ZoomPane.md) - [Frontend/Renderers/Artifacts/ArtifactRenderer](modules/Frontend_Renderers_Artifacts_ArtifactRenderer.md) - [Frontend/Renderers/GifRenderer](modules/Frontend_Renderers_GifRenderer.md) - [Frontend/Renderers/LandingPageCanvas](modules/Frontend_Renderers_LandingPageCanvas.md) - [Frontend/Renderers/PlanetscapeRenderer/PlanetIcons](modules/Frontend_Renderers_PlanetscapeRenderer_PlanetIcons.md) - [Frontend/Styles/Colors](modules/Frontend_Styles_Colors.md) - [Frontend/Styles/Mixins](modules/Frontend_Styles_Mixins.md) - [Frontend/Styles/dfstyles](modules/Frontend_Styles_dfstyles.md) - [Frontend/Utils/AppHooks](modules/Frontend_Utils_AppHooks.md) - [Frontend/Utils/BrowserChecks](modules/Frontend_Utils_BrowserChecks.md) - [Frontend/Utils/EmitterHooks](modules/Frontend_Utils_EmitterHooks.md) - [Frontend/Utils/EmitterUtils](modules/Frontend_Utils_EmitterUtils.md) - [Frontend/Utils/Hooks](modules/Frontend_Utils_Hooks.md) - [Frontend/Utils/KeyEmitters](modules/Frontend_Utils_KeyEmitters.md) - [Frontend/Utils/SettingsHooks](modules/Frontend_Utils_SettingsHooks.md) - [Frontend/Utils/ShortcutConstants](modules/Frontend_Utils_ShortcutConstants.md) - [Frontend/Utils/TerminalTypes](modules/Frontend_Utils_TerminalTypes.md) - [Frontend/Utils/TimeUtils](modules/Frontend_Utils_TimeUtils.md) - [Frontend/Utils/UIEmitter](modules/Frontend_Utils_UIEmitter.md) - [Frontend/Utils/constants](modules/Frontend_Utils_constants.md) - [Frontend/Utils/createDefinedContext](modules/Frontend_Utils_createDefinedContext.md) - [Frontend/Views/ArtifactLink](modules/Frontend_Views_ArtifactLink.md) - [Frontend/Views/ArtifactRow](modules/Frontend_Views_ArtifactRow.md) - [Frontend/Views/CadetWormhole](modules/Frontend_Views_CadetWormhole.md) - [Frontend/Views/DFErrorBoundary](modules/Frontend_Views_DFErrorBoundary.md) - [Frontend/Views/DarkForestTips](modules/Frontend_Views_DarkForestTips.md) - [Frontend/Views/EmojiPicker](modules/Frontend_Views_EmojiPicker.md) - [Frontend/Views/EmojiPlanetNotification](modules/Frontend_Views_EmojiPlanetNotification.md) - [Frontend/Views/GameWindowLayout](modules/Frontend_Views_GameWindowLayout.md) - [Frontend/Views/GenericErrorBoundary](modules/Frontend_Views_GenericErrorBoundary.md) - [Frontend/Views/LandingPageRoundArt](modules/Frontend_Views_LandingPageRoundArt.md) - [Frontend/Views/Leaderboard](modules/Frontend_Views_Leaderboard.md) - [Frontend/Views/ModalIcon](modules/Frontend_Views_ModalIcon.md) - [Frontend/Views/ModalPane](modules/Frontend_Views_ModalPane.md) - [Frontend/Views/NetworkHealth](modules/Frontend_Views_NetworkHealth.md) - [Frontend/Views/Notifications](modules/Frontend_Views_Notifications.md) - [Frontend/Views/Paused](modules/Frontend_Views_Paused.md) - [Frontend/Views/PlanetCard](modules/Frontend_Views_PlanetCard.md) - [Frontend/Views/PlanetCardComponents](modules/Frontend_Views_PlanetCardComponents.md) - [Frontend/Views/PlanetLink](modules/Frontend_Views_PlanetLink.md) - [Frontend/Views/PlanetNotifications](modules/Frontend_Views_PlanetNotifications.md) - [Frontend/Views/SendResources](modules/Frontend_Views_SendResources.md) - [Frontend/Views/Share](modules/Frontend_Views_Share.md) - [Frontend/Views/SidebarPane](modules/Frontend_Views_SidebarPane.md) - [Frontend/Views/SortableTable](modules/Frontend_Views_SortableTable.md) - [Frontend/Views/TabbedView](modules/Frontend_Views_TabbedView.md) - [Frontend/Views/Table](modules/Frontend_Views_Table.md) - [Frontend/Views/Terminal](modules/Frontend_Views_Terminal.md) - [Frontend/Views/TopBar](modules/Frontend_Views_TopBar.md) - [Frontend/Views/UpgradePreview](modules/Frontend_Views_UpgradePreview.md) - [Frontend/Views/WithdrawSilver](modules/Frontend_Views_WithdrawSilver.md) - [\_types/darkforest/api/ChunkStoreTypes](modules/types_darkforest_api_ChunkStoreTypes.md) - [\_types/darkforest/api/ContractsAPITypes](modules/types_darkforest_api_ContractsAPITypes.md) - [\_types/darkforest/api/UtilityServerAPITypes](modules/types_darkforest_api_UtilityServerAPITypes.md) - [\_types/file-loader/FileWorkerTypes](modules/types_file_loader_FileWorkerTypes.md) - [\_types/global/GlobalTypes](modules/types_global_GlobalTypes.md) ================================================ FILE: docs/classes/Backend_GameLogic_CaptureZoneGenerator.CaptureZoneGenerator.md ================================================ # Class: CaptureZoneGenerator [Backend/GameLogic/CaptureZoneGenerator](../modules/Backend_GameLogic_CaptureZoneGenerator.md).CaptureZoneGenerator Given a game start block and a zone change block interval, decide when to generate new Capture Zones. ## Table of contents ### Constructors - [constructor](Backend_GameLogic_CaptureZoneGenerator.CaptureZoneGenerator.md#constructor) ### Properties - [capturablePlanets](Backend_GameLogic_CaptureZoneGenerator.CaptureZoneGenerator.md#capturableplanets) - [changeInterval](Backend_GameLogic_CaptureZoneGenerator.CaptureZoneGenerator.md#changeinterval) - [gameManager](Backend_GameLogic_CaptureZoneGenerator.CaptureZoneGenerator.md#gamemanager) - [generated$](Backend_GameLogic_CaptureZoneGenerator.CaptureZoneGenerator.md#generated$) - [lastChangeBlock](Backend_GameLogic_CaptureZoneGenerator.CaptureZoneGenerator.md#lastchangeblock) - [nextChangeBlock](Backend_GameLogic_CaptureZoneGenerator.CaptureZoneGenerator.md#nextchangeblock) - [zones](Backend_GameLogic_CaptureZoneGenerator.CaptureZoneGenerator.md#zones) ### Accessors - [gameObjects](Backend_GameLogic_CaptureZoneGenerator.CaptureZoneGenerator.md#gameobjects) ### Methods - [\_generate](Backend_GameLogic_CaptureZoneGenerator.CaptureZoneGenerator.md#_generate) - [generate](Backend_GameLogic_CaptureZoneGenerator.CaptureZoneGenerator.md#generate) - [getNextChangeBlock](Backend_GameLogic_CaptureZoneGenerator.CaptureZoneGenerator.md#getnextchangeblock) - [getZones](Backend_GameLogic_CaptureZoneGenerator.CaptureZoneGenerator.md#getzones) - [isInZone](Backend_GameLogic_CaptureZoneGenerator.CaptureZoneGenerator.md#isinzone) - [onNewChunk](Backend_GameLogic_CaptureZoneGenerator.CaptureZoneGenerator.md#onnewchunk) - [setNextGenerationBlock](Backend_GameLogic_CaptureZoneGenerator.CaptureZoneGenerator.md#setnextgenerationblock) - [updateCapturablePlanets](Backend_GameLogic_CaptureZoneGenerator.CaptureZoneGenerator.md#updatecapturableplanets) ## Constructors ### constructor • **new CaptureZoneGenerator**(`gameManager`, `gameStartBlock`, `changeInterval`) #### Parameters | Name | Type | | :--------------- | :---------------------------------------------------- | | `gameManager` | [`default`](Backend_GameLogic_GameManager.default.md) | | `gameStartBlock` | `number` | | `changeInterval` | `number` | ## Properties ### capturablePlanets • `Private` **capturablePlanets**: `Set`<`LocationId`\> --- ### changeInterval • `Private` **changeInterval**: `number` --- ### gameManager • `Private` **gameManager**: [`default`](Backend_GameLogic_GameManager.default.md) --- ### generated$ • `Readonly` **generated$**: `Monomitter`<[`CaptureZonesGeneratedEvent`](../modules/Backend_GameLogic_CaptureZoneGenerator.md#capturezonesgeneratedevent)\> --- ### lastChangeBlock • `Private` **lastChangeBlock**: `number` --- ### nextChangeBlock • `Private` **nextChangeBlock**: `number` --- ### zones • `Private` **zones**: `Set`<`CaptureZone`\> ## Accessors ### gameObjects • `Private` `get` **gameObjects**(): [`GameObjects`](Backend_GameLogic_GameObjects.GameObjects.md) #### Returns [`GameObjects`](Backend_GameLogic_GameObjects.GameObjects.md) ## Methods ### \_generate ▸ `Private` **\_generate**(`blockNumber`): `Promise`<`Set`<`CaptureZone`\>\> #### Parameters | Name | Type | | :------------ | :------- | | `blockNumber` | `number` | #### Returns `Promise`<`Set`<`CaptureZone`\>\> --- ### generate ▸ **generate**(`blockNumber`): `Promise`<`void`\> Call when a new block is received to check if generation is needed. #### Parameters | Name | Type | Description | | :------------ | :------- | :-------------------- | | `blockNumber` | `number` | Current block number. | #### Returns `Promise`<`void`\> --- ### getNextChangeBlock ▸ **getNextChangeBlock**(): `number` The next block that will trigger a Capture Zone generation. #### Returns `number` --- ### getZones ▸ **getZones**(): `Set`<`CaptureZone`\> #### Returns `Set`<`CaptureZone`\> --- ### isInZone ▸ **isInZone**(`locationId`): `boolean` Is the given planet inside of a Capture Zone. #### Parameters | Name | Type | | :----------- | :----------- | | `locationId` | `LocationId` | #### Returns `boolean` --- ### onNewChunk ▸ `Private` **onNewChunk**(`chunk`): `void` #### Parameters | Name | Type | | :------ | :------ | | `chunk` | `Chunk` | #### Returns `void` --- ### setNextGenerationBlock ▸ `Private` **setNextGenerationBlock**(`blockNumber`): `void` #### Parameters | Name | Type | | :------------ | :------- | | `blockNumber` | `number` | #### Returns `void` --- ### updateCapturablePlanets ▸ `Private` **updateCapturablePlanets**(): `void` #### Returns `void` ================================================ FILE: docs/classes/Backend_GameLogic_ContractsAPI.ContractsAPI.md ================================================ # Class: ContractsAPI [Backend/GameLogic/ContractsAPI](../modules/Backend_GameLogic_ContractsAPI.md).ContractsAPI Roughly contains methods that map 1:1 with functions that live in the contract. Responsible for reading and writing to and from the blockchain. **`todo`** don't inherit from {@link EventEmitter}. instead use {@link Monomitter} ## Hierarchy - `EventEmitter` ↳ **`ContractsAPI`** ## Table of contents ### Constructors - [constructor](Backend_GameLogic_ContractsAPI.ContractsAPI.md#constructor) ### Properties - [contractAddress](Backend_GameLogic_ContractsAPI.ContractsAPI.md#contractaddress) - [contractCaller](Backend_GameLogic_ContractsAPI.ContractsAPI.md#contractcaller) - [ethConnection](Backend_GameLogic_ContractsAPI.ContractsAPI.md#ethconnection) - [txExecutor](Backend_GameLogic_ContractsAPI.ContractsAPI.md#txexecutor) - [MIN_BALANCE](Backend_GameLogic_ContractsAPI.ContractsAPI.md#min_balance) ### Accessors - [contract](Backend_GameLogic_ContractsAPI.ContractsAPI.md#contract) ### Methods - [afterTransaction](Backend_GameLogic_ContractsAPI.ContractsAPI.md#aftertransaction) - [beforeQueued](Backend_GameLogic_ContractsAPI.ContractsAPI.md#beforequeued) - [beforeTransaction](Backend_GameLogic_ContractsAPI.ContractsAPI.md#beforetransaction) - [bulkGetArtifacts](Backend_GameLogic_ContractsAPI.ContractsAPI.md#bulkgetartifacts) - [bulkGetArtifactsOnPlanets](Backend_GameLogic_ContractsAPI.ContractsAPI.md#bulkgetartifactsonplanets) - [bulkGetPlanets](Backend_GameLogic_ContractsAPI.ContractsAPI.md#bulkgetplanets) - [cancelTransaction](Backend_GameLogic_ContractsAPI.ContractsAPI.md#canceltransaction) - [destroy](Backend_GameLogic_ContractsAPI.ContractsAPI.md#destroy) - [emitTransactionEvents](Backend_GameLogic_ContractsAPI.ContractsAPI.md#emittransactionevents) - [getAddress](Backend_GameLogic_ContractsAPI.ContractsAPI.md#getaddress) - [getAllArrivals](Backend_GameLogic_ContractsAPI.ContractsAPI.md#getallarrivals) - [getArrival](Backend_GameLogic_ContractsAPI.ContractsAPI.md#getarrival) - [getArrivalsForPlanet](Backend_GameLogic_ContractsAPI.ContractsAPI.md#getarrivalsforplanet) - [getArtifactById](Backend_GameLogic_ContractsAPI.ContractsAPI.md#getartifactbyid) - [getConstants](Backend_GameLogic_ContractsAPI.ContractsAPI.md#getconstants) - [getContractAddress](Backend_GameLogic_ContractsAPI.ContractsAPI.md#getcontractaddress) - [getGasFeeForTransaction](Backend_GameLogic_ContractsAPI.ContractsAPI.md#getgasfeefortransaction) - [getIsPaused](Backend_GameLogic_ContractsAPI.ContractsAPI.md#getispaused) - [getPlanetById](Backend_GameLogic_ContractsAPI.ContractsAPI.md#getplanetbyid) - [getPlayerArtifacts](Backend_GameLogic_ContractsAPI.ContractsAPI.md#getplayerartifacts) - [getPlayerById](Backend_GameLogic_ContractsAPI.ContractsAPI.md#getplayerbyid) - [getPlayers](Backend_GameLogic_ContractsAPI.ContractsAPI.md#getplayers) - [getRevealedCoordsByIdIfExists](Backend_GameLogic_ContractsAPI.ContractsAPI.md#getrevealedcoordsbyidifexists) - [getRevealedPlanetsCoords](Backend_GameLogic_ContractsAPI.ContractsAPI.md#getrevealedplanetscoords) - [getTokenMintEndTimestamp](Backend_GameLogic_ContractsAPI.ContractsAPI.md#gettokenmintendtimestamp) - [getTouchedPlanetIds](Backend_GameLogic_ContractsAPI.ContractsAPI.md#gettouchedplanetids) - [getWorldRadius](Backend_GameLogic_ContractsAPI.ContractsAPI.md#getworldradius) - [makeCall](Backend_GameLogic_ContractsAPI.ContractsAPI.md#makecall) - [prioritizeTransaction](Backend_GameLogic_ContractsAPI.ContractsAPI.md#prioritizetransaction) - [removeEventListeners](Backend_GameLogic_ContractsAPI.ContractsAPI.md#removeeventlisteners) - [setDiagnosticUpdater](Backend_GameLogic_ContractsAPI.ContractsAPI.md#setdiagnosticupdater) - [setupEventListeners](Backend_GameLogic_ContractsAPI.ContractsAPI.md#setupeventlisteners) - [submitTransaction](Backend_GameLogic_ContractsAPI.ContractsAPI.md#submittransaction) ## Constructors ### constructor • **new ContractsAPI**(`__namedParameters`) #### Parameters | Name | Type | | :------------------ | :------------------- | | `__namedParameters` | `ContractsApiConfig` | #### Overrides EventEmitter.constructor ## Properties ### contractAddress • `Private` **contractAddress**: `EthAddress` The contract address is saved on the object upon construction --- ### contractCaller • `Private` `Readonly` **contractCaller**: `ContractCaller` Instrumented {@link ThrottledConcurrentQueue} for blockchain reads. --- ### ethConnection • `Readonly` **ethConnection**: `EthConnection` Our connection to the blockchain. In charge of low level networking, and also of the burner wallet. --- ### txExecutor • `Readonly` **txExecutor**: `TxExecutor` Instrumented {@link ThrottledConcurrentQueue} for blockchain writes. --- ### MIN_BALANCE ▪ `Static` `Private` `Readonly` **MIN_BALANCE**: `BigNumber` Don't allow users to submit txs if balance falls below this amount/ ## Accessors ### contract • `get` **contract**(): `DarkForest` #### Returns `DarkForest` ## Methods ### afterTransaction ▸ `Private` **afterTransaction**(`_txRequest`, `txDiagnosticInfo`): `Promise`<`void`\> #### Parameters | Name | Type | | :----------------- | :------------------------- | | `_txRequest` | `Transaction`<`TxIntent`\> | | `txDiagnosticInfo` | `unknown` | #### Returns `Promise`<`void`\> --- ### beforeQueued ▸ `Private` **beforeQueued**(`id`, `intent`, `overrides?`): `Promise`<`void`\> This function is called by {@link TxExecutor} before a transaction is queued. It gives the client an opportunity to prevent a transaction from being queued based on business logic or user interaction. Reject the promise to prevent the queued transaction from being queued. #### Parameters | Name | Type | | :----------- | :------------------- | | `id` | `number` | | `intent` | `TxIntent` | | `overrides?` | `TransactionRequest` | #### Returns `Promise`<`void`\> --- ### beforeTransaction ▸ `Private` **beforeTransaction**(`tx`): `Promise`<`void`\> This function is called by {@link TxExecutor} before each transaction. It gives the client an opportunity to prevent a transaction from going through based on business logic or user interaction. To prevent the queued transaction from being submitted, throw an Error. #### Parameters | Name | Type | | :--- | :------------------------- | | `tx` | `Transaction`<`TxIntent`\> | #### Returns `Promise`<`void`\> --- ### bulkGetArtifacts ▸ **bulkGetArtifacts**(`artifactIds`, `onProgress?`): `Promise`<`Artifact`[]\> #### Parameters | Name | Type | | :------------ | :---------------------------------------- | | `artifactIds` | `ArtifactId`[] | | `onProgress?` | (`fractionCompleted`: `number`) => `void` | #### Returns `Promise`<`Artifact`[]\> --- ### bulkGetArtifactsOnPlanets ▸ **bulkGetArtifactsOnPlanets**(`locationIds`, `onProgress?`): `Promise`<`Artifact`[][]\> #### Parameters | Name | Type | | :------------ | :---------------------------------------- | | `locationIds` | `LocationId`[] | | `onProgress?` | (`fractionCompleted`: `number`) => `void` | #### Returns `Promise`<`Artifact`[][]\> --- ### bulkGetPlanets ▸ **bulkGetPlanets**(`toLoadPlanets`, `onProgressPlanet?`, `onProgressMetadata?`): `Promise`<`Map`<`LocationId`, `Planet`\>\> #### Parameters | Name | Type | | :-------------------- | :---------------------------------------- | | `toLoadPlanets` | `LocationId`[] | | `onProgressPlanet?` | (`fractionCompleted`: `number`) => `void` | | `onProgressMetadata?` | (`fractionCompleted`: `number`) => `void` | #### Returns `Promise`<`Map`<`LocationId`, `Planet`\>\> --- ### cancelTransaction ▸ **cancelTransaction**(`tx`): `void` Remove a transaction from the queue. #### Parameters | Name | Type | | :--- | :------------------------- | | `tx` | `Transaction`<`TxIntent`\> | #### Returns `void` --- ### destroy ▸ **destroy**(): `void` #### Returns `void` --- ### emitTransactionEvents ▸ **emitTransactionEvents**(`tx`): `void` This is a strange interface between the transaction queue system and the rest of the game. The strange thing about it is that introduces another way by which transactions are pushed into the game - these {@code ContractsAPIEvent} events. #### Parameters | Name | Type | | :--- | :------------------------- | | `tx` | `Transaction`<`TxIntent`\> | #### Returns `void` --- ### getAddress ▸ **getAddress**(): `undefined` \| `EthAddress` #### Returns `undefined` \| `EthAddress` --- ### getAllArrivals ▸ **getAllArrivals**(`planetsToLoad`, `onProgress?`): `Promise`<`QueuedArrival`[]\> #### Parameters | Name | Type | | :-------------- | :---------------------------------------- | | `planetsToLoad` | `LocationId`[] | | `onProgress?` | (`fractionCompleted`: `number`) => `void` | #### Returns `Promise`<`QueuedArrival`[]\> --- ### getArrival ▸ **getArrival**(`arrivalId`): `Promise`<`undefined` \| `QueuedArrival`\> #### Parameters | Name | Type | | :---------- | :------- | | `arrivalId` | `number` | #### Returns `Promise`<`undefined` \| `QueuedArrival`\> --- ### getArrivalsForPlanet ▸ **getArrivalsForPlanet**(`planetId`): `Promise`<`QueuedArrival`[]\> #### Parameters | Name | Type | | :--------- | :----------- | | `planetId` | `LocationId` | #### Returns `Promise`<`QueuedArrival`[]\> --- ### getArtifactById ▸ **getArtifactById**(`artifactId`): `Promise`<`undefined` \| `Artifact`\> #### Parameters | Name | Type | | :----------- | :----------- | | `artifactId` | `ArtifactId` | #### Returns `Promise`<`undefined` \| `Artifact`\> --- ### getConstants ▸ **getConstants**(): `Promise`<[`ContractConstants`](../interfaces/types_darkforest_api_ContractsAPITypes.ContractConstants.md)\> #### Returns `Promise`<[`ContractConstants`](../interfaces/types_darkforest_api_ContractsAPITypes.ContractConstants.md)\> --- ### getContractAddress ▸ **getContractAddress**(): `EthAddress` #### Returns `EthAddress` --- ### getGasFeeForTransaction ▸ `Private` **getGasFeeForTransaction**(`tx`): `string` \| `AutoGasSetting` We pass this function into {@link TxExecutor} to calculate what gas fee we should use for the given transaction. The result is either a number, measured in gwei, represented as a string, or a string representing that we want to use an auto gas setting. #### Parameters | Name | Type | | :--- | :------------------------- | | `tx` | `Transaction`<`TxIntent`\> | #### Returns `string` \| `AutoGasSetting` --- ### getIsPaused ▸ **getIsPaused**(): `Promise`<`boolean`\> #### Returns `Promise`<`boolean`\> --- ### getPlanetById ▸ **getPlanetById**(`planetId`): `Promise`<`undefined` \| `Planet`\> #### Parameters | Name | Type | | :--------- | :----------- | | `planetId` | `LocationId` | #### Returns `Promise`<`undefined` \| `Planet`\> --- ### getPlayerArtifacts ▸ **getPlayerArtifacts**(`playerId?`, `onProgress?`): `Promise`<`Artifact`[]\> #### Parameters | Name | Type | | :------------ | :------------------------------ | | `playerId?` | `EthAddress` | | `onProgress?` | (`percent`: `number`) => `void` | #### Returns `Promise`<`Artifact`[]\> --- ### getPlayerById ▸ **getPlayerById**(`playerId`): `Promise`<`undefined` \| `Player`\> #### Parameters | Name | Type | | :--------- | :----------- | | `playerId` | `EthAddress` | #### Returns `Promise`<`undefined` \| `Player`\> --- ### getPlayers ▸ **getPlayers**(`onProgress?`): `Promise`<`Map`<`string`, `Player`\>\> #### Parameters | Name | Type | | :------------ | :---------------------------------------- | | `onProgress?` | (`fractionCompleted`: `number`) => `void` | #### Returns `Promise`<`Map`<`string`, `Player`\>\> --- ### getRevealedCoordsByIdIfExists ▸ **getRevealedCoordsByIdIfExists**(`planetId`): `Promise`<`undefined` \| `RevealedCoords`\> #### Parameters | Name | Type | | :--------- | :----------- | | `planetId` | `LocationId` | #### Returns `Promise`<`undefined` \| `RevealedCoords`\> --- ### getRevealedPlanetsCoords ▸ **getRevealedPlanetsCoords**(`startingAt`, `onProgressIds?`, `onProgressCoords?`): `Promise`<`RevealedCoords`[]\> #### Parameters | Name | Type | | :------------------ | :---------------------------------------- | | `startingAt` | `number` | | `onProgressIds?` | (`fractionCompleted`: `number`) => `void` | | `onProgressCoords?` | (`fractionCompleted`: `number`) => `void` | #### Returns `Promise`<`RevealedCoords`[]\> --- ### getTokenMintEndTimestamp ▸ **getTokenMintEndTimestamp**(): `Promise`<`number`\> #### Returns `Promise`<`number`\> --- ### getTouchedPlanetIds ▸ **getTouchedPlanetIds**(`startingAt`, `onProgress?`): `Promise`<`LocationId`[]\> #### Parameters | Name | Type | | :------------ | :---------------------------------------- | | `startingAt` | `number` | | `onProgress?` | (`fractionCompleted`: `number`) => `void` | #### Returns `Promise`<`LocationId`[]\> --- ### getWorldRadius ▸ **getWorldRadius**(): `Promise`<`number`\> #### Returns `Promise`<`number`\> --- ### makeCall ▸ `Private` **makeCall**<`T`\>(`contractViewFunction`, `args?`): `Promise`<`T`\> #### Type parameters | Name | | :--- | | `T` | #### Parameters | Name | Type | Default value | | :--------------------- | :----------------------- | :------------ | | `contractViewFunction` | `ContractFunction`<`T`\> | `undefined` | | `args` | `unknown`[] | `[]` | #### Returns `Promise`<`T`\> --- ### prioritizeTransaction ▸ **prioritizeTransaction**(`tx`): `void` Make sure this transaction is the next to be executed. #### Parameters | Name | Type | | :--- | :------------------------- | | `tx` | `Transaction`<`TxIntent`\> | #### Returns `void` --- ### removeEventListeners ▸ **removeEventListeners**(): `void` #### Returns `void` --- ### setDiagnosticUpdater ▸ **setDiagnosticUpdater**(`diagnosticUpdater?`): `void` #### Parameters | Name | Type | | :------------------- | :------------------ | | `diagnosticUpdater?` | `DiagnosticUpdater` | #### Returns `void` --- ### setupEventListeners ▸ **setupEventListeners**(): `Promise`<`void`\> #### Returns `Promise`<`void`\> --- ### submitTransaction ▸ **submitTransaction**<`T`\>(`txIntent`, `overrides?`): `Promise`<`Transaction`<`T`\>\> #### Type parameters | Name | Type | | :--- | :----------------- | | `T` | extends `TxIntent` | #### Parameters | Name | Type | | :----------- | :------------------- | | `txIntent` | `T` | | `overrides?` | `TransactionRequest` | #### Returns `Promise`<`Transaction`<`T`\>\> ================================================ FILE: docs/classes/Backend_GameLogic_GameManager.default.md ================================================ # Class: default [Backend/GameLogic/GameManager](../modules/Backend_GameLogic_GameManager.md).default ## Hierarchy - `EventEmitter` ↳ **`default`** ## Table of contents ### Constructors - [constructor](Backend_GameLogic_GameManager.default.md#constructor) ### Properties - [account](Backend_GameLogic_GameManager.default.md#account) - [captureZoneGenerator](Backend_GameLogic_GameManager.default.md#capturezonegenerator) - [contractConstants](Backend_GameLogic_GameManager.default.md#contractconstants) - [contractsAPI](Backend_GameLogic_GameManager.default.md#contractsapi) - [diagnostics](Backend_GameLogic_GameManager.default.md#diagnostics) - [diagnosticsInterval](Backend_GameLogic_GameManager.default.md#diagnosticsinterval) - [endTimeSeconds](Backend_GameLogic_GameManager.default.md#endtimeseconds) - [entityStore](Backend_GameLogic_GameManager.default.md#entitystore) - [ethConnection](Backend_GameLogic_GameManager.default.md#ethconnection) - [hashConfig](Backend_GameLogic_GameManager.default.md#hashconfig) - [hashRate](Backend_GameLogic_GameManager.default.md#hashrate) - [homeLocation](Backend_GameLogic_GameManager.default.md#homelocation) - [minerManager](Backend_GameLogic_GameManager.default.md#minermanager) - [networkHealth$](Backend_GameLogic_GameManager.default.md#networkhealth$) - [networkHealthInterval](Backend_GameLogic_GameManager.default.md#networkhealthinterval) - [paused](Backend_GameLogic_GameManager.default.md#paused) - [paused$](Backend_GameLogic_GameManager.default.md#paused$) - [persistentChunkStore](Backend_GameLogic_GameManager.default.md#persistentchunkstore) - [planetHashMimc](Backend_GameLogic_GameManager.default.md#planethashmimc) - [playerInterval](Backend_GameLogic_GameManager.default.md#playerinterval) - [players](Backend_GameLogic_GameManager.default.md#players) - [playersUpdated$](Backend_GameLogic_GameManager.default.md#playersupdated$) - [safeMode](Backend_GameLogic_GameManager.default.md#safemode) - [scoreboardInterval](Backend_GameLogic_GameManager.default.md#scoreboardinterval) - [settingsSubscription](Backend_GameLogic_GameManager.default.md#settingssubscription) - [snarkHelper](Backend_GameLogic_GameManager.default.md#snarkhelper) - [terminal](Backend_GameLogic_GameManager.default.md#terminal) - [useMockHash](Backend_GameLogic_GameManager.default.md#usemockhash) - [worldRadius](Backend_GameLogic_GameManager.default.md#worldradius) ### Accessors - [captureZoneGeneratedEmitter](Backend_GameLogic_GameManager.default.md#capturezonegeneratedemitter) - [planetRarity](Backend_GameLogic_GameManager.default.md#planetrarity) ### Methods - [activateArtifact](Backend_GameLogic_GameManager.default.md#activateartifact) - [addAccount](Backend_GameLogic_GameManager.default.md#addaccount) - [addNewChunk](Backend_GameLogic_GameManager.default.md#addnewchunk) - [biomebasePerlin](Backend_GameLogic_GameManager.default.md#biomebaseperlin) - [bulkAddNewChunks](Backend_GameLogic_GameManager.default.md#bulkaddnewchunks) - [bulkHardRefreshPlanets](Backend_GameLogic_GameManager.default.md#bulkhardrefreshplanets) - [buyHat](Backend_GameLogic_GameManager.default.md#buyhat) - [capturePlanet](Backend_GameLogic_GameManager.default.md#captureplanet) - [checkGameHasEnded](Backend_GameLogic_GameManager.default.md#checkgamehasended) - [clearEmoji](Backend_GameLogic_GameManager.default.md#clearemoji) - [deactivateArtifact](Backend_GameLogic_GameManager.default.md#deactivateartifact) - [depositArtifact](Backend_GameLogic_GameManager.default.md#depositartifact) - [destroy](Backend_GameLogic_GameManager.default.md#destroy) - [findArtifact](Backend_GameLogic_GameManager.default.md#findartifact) - [findRandomHomePlanet](Backend_GameLogic_GameManager.default.md#findrandomhomeplanet) - [forceTick](Backend_GameLogic_GameManager.default.md#forcetick) - [getAccount](Backend_GameLogic_GameManager.default.md#getaccount) - [getActiveArtifact](Backend_GameLogic_GameManager.default.md#getactiveartifact) - [getAddress](Backend_GameLogic_GameManager.default.md#getaddress) - [getAllOwnedPlanets](Backend_GameLogic_GameManager.default.md#getallownedplanets) - [getAllPlanets](Backend_GameLogic_GameManager.default.md#getallplanets) - [getAllPlayers](Backend_GameLogic_GameManager.default.md#getallplayers) - [getAllVoyages](Backend_GameLogic_GameManager.default.md#getallvoyages) - [getArtifactMap](Backend_GameLogic_GameManager.default.md#getartifactmap) - [getArtifactUpdated$](Backend_GameLogic_GameManager.default.md#getartifactupdated$) - [getArtifactWithId](Backend_GameLogic_GameManager.default.md#getartifactwithid) - [getArtifactsWithIds](Backend_GameLogic_GameManager.default.md#getartifactswithids) - [getCaptureZoneGenerator](Backend_GameLogic_GameManager.default.md#getcapturezonegenerator) - [getCaptureZones](Backend_GameLogic_GameManager.default.md#getcapturezones) - [getChunk](Backend_GameLogic_GameManager.default.md#getchunk) - [getChunkStore](Backend_GameLogic_GameManager.default.md#getchunkstore) - [getClaimedLocations](Backend_GameLogic_GameManager.default.md#getclaimedlocations) - [getConstructors](Backend_GameLogic_GameManager.default.md#getconstructors) - [getContract](Backend_GameLogic_GameManager.default.md#getcontract) - [getContractAPI](Backend_GameLogic_GameManager.default.md#getcontractapi) - [getContractAddress](Backend_GameLogic_GameManager.default.md#getcontractaddress) - [getContractConstants](Backend_GameLogic_GameManager.default.md#getcontractconstants) - [getCurrentlyExploringChunk](Backend_GameLogic_GameManager.default.md#getcurrentlyexploringchunk) - [getDefaultSpaceJunkForPlanetLevel](Backend_GameLogic_GameManager.default.md#getdefaultspacejunkforplanetlevel) - [getDiagnostics](Backend_GameLogic_GameManager.default.md#getdiagnostics) - [getDist](Backend_GameLogic_GameManager.default.md#getdist) - [getDistCoords](Backend_GameLogic_GameManager.default.md#getdistcoords) - [getEndTimeSeconds](Backend_GameLogic_GameManager.default.md#getendtimeseconds) - [getEnergyArrivingForMove](Backend_GameLogic_GameManager.default.md#getenergyarrivingformove) - [getEnergyCurveAtPercent](Backend_GameLogic_GameManager.default.md#getenergycurveatpercent) - [getEnergyNeededForMove](Backend_GameLogic_GameManager.default.md#getenergyneededformove) - [getEnergyOfPlayer](Backend_GameLogic_GameManager.default.md#getenergyofplayer) - [getEthConnection](Backend_GameLogic_GameManager.default.md#getethconnection) - [getExploredChunks](Backend_GameLogic_GameManager.default.md#getexploredchunks) - [getGameObjects](Backend_GameLogic_GameManager.default.md#getgameobjects) - [getHashConfig](Backend_GameLogic_GameManager.default.md#gethashconfig) - [getHashesPerSec](Backend_GameLogic_GameManager.default.md#gethashespersec) - [getHomeCoords](Backend_GameLogic_GameManager.default.md#gethomecoords) - [getHomeHash](Backend_GameLogic_GameManager.default.md#gethomehash) - [getLocationOfPlanet](Backend_GameLogic_GameManager.default.md#getlocationofplanet) - [getMaxMoveDist](Backend_GameLogic_GameManager.default.md#getmaxmovedist) - [getMiningPattern](Backend_GameLogic_GameManager.default.md#getminingpattern) - [getMyArtifactMap](Backend_GameLogic_GameManager.default.md#getmyartifactmap) - [getMyArtifacts](Backend_GameLogic_GameManager.default.md#getmyartifacts) - [getMyArtifactsUpdated$](Backend_GameLogic_GameManager.default.md#getmyartifactsupdated$) - [getMyBalance](Backend_GameLogic_GameManager.default.md#getmybalance) - [getMyBalance$](Backend_GameLogic_GameManager.default.md#getmybalance$) - [getMyBalanceEth](Backend_GameLogic_GameManager.default.md#getmybalanceeth) - [getMyPlanetMap](Backend_GameLogic_GameManager.default.md#getmyplanetmap) - [getMyPlanets](Backend_GameLogic_GameManager.default.md#getmyplanets) - [getMyPlanetsUpdated$](Backend_GameLogic_GameManager.default.md#getmyplanetsupdated$) - [getMyScore](Backend_GameLogic_GameManager.default.md#getmyscore) - [getNextBroadcastAvailableTimestamp](Backend_GameLogic_GameManager.default.md#getnextbroadcastavailabletimestamp) - [getNextClaimAvailableTimestamp](Backend_GameLogic_GameManager.default.md#getnextclaimavailabletimestamp) - [getNextRevealCountdownInfo](Backend_GameLogic_GameManager.default.md#getnextrevealcountdowninfo) - [getNotificationsManager](Backend_GameLogic_GameManager.default.md#getnotificationsmanager) - [getPaused](Backend_GameLogic_GameManager.default.md#getpaused) - [getPaused$](Backend_GameLogic_GameManager.default.md#getpaused$) - [getPerlinThresholds](Backend_GameLogic_GameManager.default.md#getperlinthresholds) - [getPlanetLevel](Backend_GameLogic_GameManager.default.md#getplanetlevel) - [getPlanetMap](Backend_GameLogic_GameManager.default.md#getplanetmap) - [getPlanetRarity](Backend_GameLogic_GameManager.default.md#getplanetrarity) - [getPlanetUpdated$](Backend_GameLogic_GameManager.default.md#getplanetupdated$) - [getPlanetWithCoords](Backend_GameLogic_GameManager.default.md#getplanetwithcoords) - [getPlanetWithId](Backend_GameLogic_GameManager.default.md#getplanetwithid) - [getPlanetsInRange](Backend_GameLogic_GameManager.default.md#getplanetsinrange) - [getPlanetsInWorldRectangle](Backend_GameLogic_GameManager.default.md#getplanetsinworldrectangle) - [getPlanetsWithIds](Backend_GameLogic_GameManager.default.md#getplanetswithids) - [getPlayer](Backend_GameLogic_GameManager.default.md#getplayer) - [getPlayerScore](Backend_GameLogic_GameManager.default.md#getplayerscore) - [getPlayerSpaceJunk](Backend_GameLogic_GameManager.default.md#getplayerspacejunk) - [getPlayerSpaceJunkLimit](Backend_GameLogic_GameManager.default.md#getplayerspacejunklimit) - [getPrivateKey](Backend_GameLogic_GameManager.default.md#getprivatekey) - [getRangeBuff](Backend_GameLogic_GameManager.default.md#getrangebuff) - [getRevealedLocations](Backend_GameLogic_GameManager.default.md#getrevealedlocations) - [getSafeMode](Backend_GameLogic_GameManager.default.md#getsafemode) - [getSignedTwitter](Backend_GameLogic_GameManager.default.md#getsignedtwitter) - [getSilverCurveAtPercent](Backend_GameLogic_GameManager.default.md#getsilvercurveatpercent) - [getSilverOfPlayer](Backend_GameLogic_GameManager.default.md#getsilverofplayer) - [getSnarkHelper](Backend_GameLogic_GameManager.default.md#getsnarkhelper) - [getSpaceships](Backend_GameLogic_GameManager.default.md#getspaceships) - [getSpeedBuff](Backend_GameLogic_GameManager.default.md#getspeedbuff) - [getStalePlanetWithId](Backend_GameLogic_GameManager.default.md#getstaleplanetwithid) - [getTemperature](Backend_GameLogic_GameManager.default.md#gettemperature) - [getTimeForMove](Backend_GameLogic_GameManager.default.md#gettimeformove) - [getTokenMintEndTimeSeconds](Backend_GameLogic_GameManager.default.md#gettokenmintendtimeseconds) - [getTwitter](Backend_GameLogic_GameManager.default.md#gettwitter) - [getUIEventEmitter](Backend_GameLogic_GameManager.default.md#getuieventemitter) - [getUnconfirmedMoves](Backend_GameLogic_GameManager.default.md#getunconfirmedmoves) - [getUnconfirmedUpgrades](Backend_GameLogic_GameManager.default.md#getunconfirmedupgrades) - [getUnconfirmedWormholeActivations](Backend_GameLogic_GameManager.default.md#getunconfirmedwormholeactivations) - [getUniverseTotalEnergy](Backend_GameLogic_GameManager.default.md#getuniversetotalenergy) - [getUpgrade](Backend_GameLogic_GameManager.default.md#getupgrade) - [getWorldRadius](Backend_GameLogic_GameManager.default.md#getworldradius) - [getWorldSilver](Backend_GameLogic_GameManager.default.md#getworldsilver) - [getWormholeFactors](Backend_GameLogic_GameManager.default.md#getwormholefactors) - [getWormholes](Backend_GameLogic_GameManager.default.md#getwormholes) - [hardRefreshArtifact](Backend_GameLogic_GameManager.default.md#hardrefreshartifact) - [hardRefreshPlanet](Backend_GameLogic_GameManager.default.md#hardrefreshplanet) - [hardRefreshPlayer](Backend_GameLogic_GameManager.default.md#hardrefreshplayer) - [hasJoinedGame](Backend_GameLogic_GameManager.default.md#hasjoinedgame) - [hasMinedChunk](Backend_GameLogic_GameManager.default.md#hasminedchunk) - [initMiningManager](Backend_GameLogic_GameManager.default.md#initminingmanager) - [invadePlanet](Backend_GameLogic_GameManager.default.md#invadeplanet) - [isAdmin](Backend_GameLogic_GameManager.default.md#isadmin) - [isMining](Backend_GameLogic_GameManager.default.md#ismining) - [isPlanetMineable](Backend_GameLogic_GameManager.default.md#isplanetmineable) - [isRoundOver](Backend_GameLogic_GameManager.default.md#isroundover) - [joinGame](Backend_GameLogic_GameManager.default.md#joingame) - [listenForNewBlock](Backend_GameLogic_GameManager.default.md#listenfornewblock) - [loadContract](Backend_GameLogic_GameManager.default.md#loadcontract) - [loadPlugins](Backend_GameLogic_GameManager.default.md#loadplugins) - [locationBigIntFromCoords](Backend_GameLogic_GameManager.default.md#locationbigintfromcoords) - [locationFromCoords](Backend_GameLogic_GameManager.default.md#locationfromcoords) - [move](Backend_GameLogic_GameManager.default.md#move) - [onTxCancelled](Backend_GameLogic_GameManager.default.md#ontxcancelled) - [onTxConfirmed](Backend_GameLogic_GameManager.default.md#ontxconfirmed) - [onTxReverted](Backend_GameLogic_GameManager.default.md#ontxreverted) - [onTxSubmit](Backend_GameLogic_GameManager.default.md#ontxsubmit) - [prospectPlanet](Backend_GameLogic_GameManager.default.md#prospectplanet) - [refreshNetworkHealth](Backend_GameLogic_GameManager.default.md#refreshnetworkhealth) - [refreshScoreboard](Backend_GameLogic_GameManager.default.md#refreshscoreboard) - [refreshServerPlanetStates](Backend_GameLogic_GameManager.default.md#refreshserverplanetstates) - [refreshTwitters](Backend_GameLogic_GameManager.default.md#refreshtwitters) - [revealLocation](Backend_GameLogic_GameManager.default.md#reveallocation) - [savePlugins](Backend_GameLogic_GameManager.default.md#saveplugins) - [setMinerCores](Backend_GameLogic_GameManager.default.md#setminercores) - [setMiningPattern](Backend_GameLogic_GameManager.default.md#setminingpattern) - [setPlanetEmoji](Backend_GameLogic_GameManager.default.md#setplanetemoji) - [setPlayerTwitters](Backend_GameLogic_GameManager.default.md#setplayertwitters) - [setRadius](Backend_GameLogic_GameManager.default.md#setradius) - [setSafeMode](Backend_GameLogic_GameManager.default.md#setsafemode) - [setSnarkCacheSize](Backend_GameLogic_GameManager.default.md#setsnarkcachesize) - [softRefreshPlanet](Backend_GameLogic_GameManager.default.md#softrefreshplanet) - [spaceTypeFromPerlin](Backend_GameLogic_GameManager.default.md#spacetypefromperlin) - [spaceTypePerlin](Backend_GameLogic_GameManager.default.md#spacetypeperlin) - [startExplore](Backend_GameLogic_GameManager.default.md#startexplore) - [stopExplore](Backend_GameLogic_GameManager.default.md#stopexplore) - [submitDisconnectTwitter](Backend_GameLogic_GameManager.default.md#submitdisconnecttwitter) - [submitPlanetMessage](Backend_GameLogic_GameManager.default.md#submitplanetmessage) - [submitTransaction](Backend_GameLogic_GameManager.default.md#submittransaction) - [submitVerifyTwitter](Backend_GameLogic_GameManager.default.md#submitverifytwitter) - [testNotification](Backend_GameLogic_GameManager.default.md#testnotification) - [timeUntilNextBroadcastAvailable](Backend_GameLogic_GameManager.default.md#timeuntilnextbroadcastavailable) - [transferOwnership](Backend_GameLogic_GameManager.default.md#transferownership) - [updateDiagnostics](Backend_GameLogic_GameManager.default.md#updatediagnostics) - [upgrade](Backend_GameLogic_GameManager.default.md#upgrade) - [uploadDiagnostics](Backend_GameLogic_GameManager.default.md#uploaddiagnostics) - [verifyMessage](Backend_GameLogic_GameManager.default.md#verifymessage) - [waitForPlanet](Backend_GameLogic_GameManager.default.md#waitforplanet) - [withdrawArtifact](Backend_GameLogic_GameManager.default.md#withdrawartifact) - [withdrawSilver](Backend_GameLogic_GameManager.default.md#withdrawsilver) - [create](Backend_GameLogic_GameManager.default.md#create) ## Constructors ### constructor • `Private` **new default**(`terminal`, `account`, `players`, `touchedPlanets`, `allTouchedPlanetIds`, `revealedCoords`, `claimedCoords`, `worldRadius`, `unprocessedArrivals`, `unprocessedPlanetArrivalIds`, `contractsAPI`, `contractConstants`, `persistentChunkStore`, `snarkHelper`, `homeLocation`, `useMockHash`, `artifacts`, `ethConnection`, `paused`) #### Parameters | Name | Type | | :---------------------------- | :-------------------------------------------------------------------------------------------------------------- | | `terminal` | `MutableRefObject`<`undefined` \| [`TerminalHandle`](../interfaces/Frontend_Views_Terminal.TerminalHandle.md)\> | | `account` | `undefined` \| `EthAddress` | | `players` | `Map`<`string`, `Player`\> | | `touchedPlanets` | `Map`<`LocationId`, `Planet`\> | | `allTouchedPlanetIds` | `Set`<`LocationId`\> | | `revealedCoords` | `Map`<`LocationId`, `RevealedCoords`\> | | `claimedCoords` | `Map`<`LocationId`, `ClaimedCoords`\> | | `worldRadius` | `number` | | `unprocessedArrivals` | `Map`<`VoyageId`, `QueuedArrival`\> | | `unprocessedPlanetArrivalIds` | `Map`<`LocationId`, `VoyageId`[]\> | | `contractsAPI` | [`ContractsAPI`](Backend_GameLogic_ContractsAPI.ContractsAPI.md) | | `contractConstants` | [`ContractConstants`](../interfaces/types_darkforest_api_ContractsAPITypes.ContractConstants.md) | | `persistentChunkStore` | [`default`](Backend_Storage_PersistentChunkStore.default.md) | | `snarkHelper` | [`default`](Backend_Utils_SnarkArgsHelper.default.md) | | `homeLocation` | `undefined` \| `WorldLocation` | | `useMockHash` | `boolean` | | `artifacts` | `Map`<`ArtifactId`, `Artifact`\> | | `ethConnection` | `EthConnection` | | `paused` | `boolean` | #### Overrides EventEmitter.constructor ## Properties ### account • `Private` `Readonly` **account**: `undefined` \| `EthAddress` The ethereum address of the player who is currently logged in. We support 'no account', represented by `undefined` in the case when you want to simply load the game state from the contract and view it without be able to make any moves. --- ### captureZoneGenerator • `Private` **captureZoneGenerator**: `undefined` \| [`CaptureZoneGenerator`](Backend_GameLogic_CaptureZoneGenerator.CaptureZoneGenerator.md) Generates capture zones. --- ### contractConstants • `Private` `Readonly` **contractConstants**: [`ContractConstants`](../interfaces/types_darkforest_api_ContractsAPITypes.ContractConstants.md) Game parameters set by the contract. Stuff like perlin keys, which are important for mining the correct universe, or the time multiplier, which allows us to tune how quickly voyages go. **`todo`** move this into a separate `GameConfiguration` class. --- ### contractsAPI • `Private` `Readonly` **contractsAPI**: [`ContractsAPI`](Backend_GameLogic_ContractsAPI.ContractsAPI.md) Allows us to make contract calls, and execute transactions. Be careful about how you use this guy. You don't want to cause your client to send an excessive amount of traffic to whatever node you're connected to. Interacting with the blockchain isn't free, and we need to be mindful about about the way our application interacts with the blockchain. The current rate limiting strategy consists of three points: - data that needs to be fetched often should be fetched in bulk. - rate limit smart contract calls (reads from the blockchain), implemented by {@link ContractCaller} and transactions (writes to the blockchain on behalf of the player), implemented by {@link TxExecutor} via two separately tuned {@link ThrottledConcurrentQueue}s. --- ### diagnostics • `Private` **diagnostics**: `Diagnostics` Diagnostic information about the game. --- ### diagnosticsInterval • `Private` **diagnosticsInterval**: `Timer` Handle to an interval that periodically uploads diagnostic information from this client. --- ### endTimeSeconds • `Private` `Readonly` **endTimeSeconds**: `number` = `1948939200` **`todo`** change this to the correct timestamp each round. --- ### entityStore • `Private` `Readonly` **entityStore**: [`GameObjects`](Backend_GameLogic_GameObjects.GameObjects.md) This variable contains the internal state of objects that live in the game world. --- ### ethConnection • `Private` `Readonly` **ethConnection**: `EthConnection` An interface to the blockchain that is a little bit lower-level than [ContractsAPI](Backend_GameLogic_ContractsAPI.ContractsAPI.md). It allows us to do basic operations such as wait for a transaction to complete, check the player's address and balance, etc. --- ### hashConfig • `Private` `Readonly` **hashConfig**: [`HashConfig`](../modules/types_global_GlobalTypes.md#hashconfig) Each round we change the hash configuration of the game. The hash configuration is download from the blockchain, and essentially acts as a salt, permuting the universe into a unique configuration for each new round. **`todo`** deduplicate this and `useMockHash` somehow. --- ### hashRate • `Private` **hashRate**: `number` Continuously updated value representing the total hashes per second that the game is currently mining the universe at. **`todo`** keep this in {@link MinerManager} --- ### homeLocation • `Private` **homeLocation**: `undefined` \| `WorldLocation` The spawn location of the current player. **`todo,`** make this smarter somehow. It's really annoying to have to import world coordinates, and get them wrong or something. Maybe we need to mark a planet, once it's been initialized contract-side, as the homeworld of the user who initialized on it. That way, when you import a new account into the game, and you import map data that contains your home planet, the client would be able to automatically detect which planet is the player's home planet. **`todo`** move this into a new `PlayerState` class. --- ### minerManager • `Private` `Optional` **minerManager**: [`default`](Backend_Miner_MinerManager.default.md) Manages the process of mining new space territory. --- ### networkHealth$ • **networkHealth$**: `Monomitter`<`NetworkHealthSummary`\> Emits whenever we load the network health summary from the webserver, which is derived from diagnostics that the client sends up to the webserver as well. --- ### networkHealthInterval • `Private` **networkHealthInterval**: `Timer` Handle to an interval that periodically refreshes the network's health from our webserver. --- ### paused • `Private` **paused**: `boolean` --- ### paused$ • **paused$**: `Monomitter`<`boolean`\> --- ### persistentChunkStore • `Private` `Readonly` **persistentChunkStore**: [`default`](Backend_Storage_PersistentChunkStore.default.md) An object that syncs any newly added or deleted chunks to the player's IndexedDB. **`todo`** it also persists other game data to IndexedDB. This class needs to be renamed `GameSaver` or something like that. --- ### planetHashMimc • `Private` `Readonly` **planetHashMimc**: (...`inputs`: `number`[]) => `BigInteger` #### Type declaration ▸ (...`inputs`): `BigInteger` The aforementioned hash function. In debug mode where `DISABLE_ZK_CHECKS` is on, we use a faster hash function. Othewise, in production mode, use MiMC hash (https://byt3bit.github.io/primesym/). ##### Parameters | Name | Type | | :---------- | :--------- | | `...inputs` | `number`[] | ##### Returns `BigInteger` --- ### playerInterval • `Private` **playerInterval**: `Timer` Handle to an interval that periodically refreshes some information about the player from the blockchain. **`todo`** move this into a new `PlayerState` class. --- ### players • `Private` `Readonly` **players**: `Map`<`string`, `Player`\> Map from ethereum addresses to player objects. This isn't stored in [GameObjects](Backend_GameLogic_GameObjects.GameObjects.md), because it's not techincally an entity that exists in the world. A player just controls planets and artifacts that do exist in the world. **`todo`** move this into a new `Players` class. --- ### playersUpdated$ • `Readonly` **playersUpdated$**: `Monomitter`<`void`\> Whenever we refresh the players twitter accounts or scores, we publish an event here. --- ### safeMode • `Private` **safeMode**: `boolean` Setting to allow players to start game without plugins that were running during the previous run of the game client. By default, the game launches plugins that were running that were running when the game was last closed. --- ### scoreboardInterval • `Private` **scoreboardInterval**: `Timer` Handle to an interval that periodically refreshes the scoreboard from our webserver. --- ### settingsSubscription • `Private` **settingsSubscription**: `undefined` \| `Subscription` Subscription to act on setting changes --- ### snarkHelper • `Private` `Readonly` **snarkHelper**: [`default`](Backend_Utils_SnarkArgsHelper.default.md) Responsible for generating snark proofs. --- ### terminal • `Private` `Readonly` **terminal**: `MutableRefObject`<`undefined` \| [`TerminalHandle`](../interfaces/Frontend_Views_Terminal.TerminalHandle.md)\> Kind of hacky, but we store a reference to the terminal that the player sees when the initially load into the game. This is the same exact terminal that appears inside the collapsable right bar of the game. --- ### useMockHash • `Private` `Readonly` **useMockHash**: `boolean` In debug builds of the game, we can connect to a set of contracts deployed to a local blockchain, which are tweaked to not verify planet hashes, meaning we can use a faster hash function with similar properties to mimc. This allows us to mine the map faster in debug mode. **`todo`** move this into a separate `GameConfiguration` class. --- ### worldRadius • `Private` **worldRadius**: `number` Sometimes the universe gets bigger... Sometimes it doesn't. **`todo`** move this into a new `GameConfiguration` class. ## Accessors ### captureZoneGeneratedEmitter • `get` **captureZoneGeneratedEmitter**(): `undefined` \| `Monomitter`<[`CaptureZonesGeneratedEvent`](../modules/Backend_GameLogic_CaptureZoneGenerator.md#capturezonesgeneratedevent)\> Emits when new capture zones are generated. #### Returns `undefined` \| `Monomitter`<[`CaptureZonesGeneratedEvent`](../modules/Backend_GameLogic_CaptureZoneGenerator.md#capturezonesgeneratedevent)\> --- ### planetRarity • `get` **planetRarity**(): `number` #### Returns `number` ## Methods ### activateArtifact ▸ **activateArtifact**(`locationId`, `artifactId`, `wormholeTo`, `bypassChecks?`): `Promise`<`Transaction`<`UnconfirmedActivateArtifact`\>\> #### Parameters | Name | Type | Default value | | :------------- | :-------------------------- | :------------ | | `locationId` | `LocationId` | `undefined` | | `artifactId` | `ArtifactId` | `undefined` | | `wormholeTo` | `undefined` \| `LocationId` | `undefined` | | `bypassChecks` | `boolean` | `false` | #### Returns `Promise`<`Transaction`<`UnconfirmedActivateArtifact`\>\> --- ### addAccount ▸ **addAccount**(`coords`): `Promise`<`boolean`\> Initializes a new player's game to start at the given home planet. Must have already initialized the player on the contract. #### Parameters | Name | Type | | :------- | :------------ | | `coords` | `WorldCoords` | #### Returns `Promise`<`boolean`\> --- ### addNewChunk ▸ **addNewChunk**(`chunk`): [`default`](Backend_GameLogic_GameManager.default.md) Makes this game manager aware of a new chunk - which includes its location, size, as well as all of the planets contained in that chunk. Causes the client to load all of the information about those planets from the blockchain. #### Parameters | Name | Type | | :------ | :------ | | `chunk` | `Chunk` | #### Returns [`default`](Backend_GameLogic_GameManager.default.md) --- ### biomebasePerlin ▸ **biomebasePerlin**(`coords`, `floor`): `number` Gets the biome perlin valie at the given location in the world. #### Parameters | Name | Type | | :------- | :------------ | | `coords` | `WorldCoords` | | `floor` | `boolean` | #### Returns `number` --- ### bulkAddNewChunks ▸ **bulkAddNewChunks**(`chunks`): `Promise`<`void`\> To add multiple chunks at once, use this function rather than `addNewChunk`, in order to load all of the associated planet data in an efficient manner. #### Parameters | Name | Type | | :------- | :-------- | | `chunks` | `Chunk`[] | #### Returns `Promise`<`void`\> --- ### bulkHardRefreshPlanets ▸ `Private` **bulkHardRefreshPlanets**(`planetIds`): `Promise`<`void`\> #### Parameters | Name | Type | | :---------- | :------------- | | `planetIds` | `LocationId`[] | #### Returns `Promise`<`void`\> --- ### buyHat ▸ **buyHat**(`planetId`, `_bypassChecks?`): `Promise`<`Transaction`<`UnconfirmedBuyHat`\>\> Submits a transaction to the blockchain to buy a hat for the given planet. You must own the planet. Warning costs real xdai. Hats are permanently locked to a planet. They are purely cosmetic and a great way to BM your opponents or just look your best. Just like in the real world, more money means more hat. #### Parameters | Name | Type | Default value | | :-------------- | :----------- | :------------ | | `planetId` | `LocationId` | `undefined` | | `_bypassChecks` | `boolean` | `false` | #### Returns `Promise`<`Transaction`<`UnconfirmedBuyHat`\>\> --- ### capturePlanet ▸ **capturePlanet**(`locationId`): `Promise`<`Transaction`<`UnconfirmedCapturePlanet`\>\> #### Parameters | Name | Type | | :----------- | :----------- | | `locationId` | `LocationId` | #### Returns `Promise`<`Transaction`<`UnconfirmedCapturePlanet`\>\> --- ### checkGameHasEnded ▸ `Private` **checkGameHasEnded**(): `boolean` #### Returns `boolean` --- ### clearEmoji ▸ **clearEmoji**(`locationId`): `Promise`<`void`\> If you are the owner of this planet, you can delete the emoji that is hovering above the planet. #### Parameters | Name | Type | | :----------- | :----------- | | `locationId` | `LocationId` | #### Returns `Promise`<`void`\> --- ### deactivateArtifact ▸ **deactivateArtifact**(`locationId`, `artifactId`, `bypassChecks?`): `Promise`<`Transaction`<`UnconfirmedDeactivateArtifact`\>\> #### Parameters | Name | Type | Default value | | :------------- | :----------- | :------------ | | `locationId` | `LocationId` | `undefined` | | `artifactId` | `ArtifactId` | `undefined` | | `bypassChecks` | `boolean` | `false` | #### Returns `Promise`<`Transaction`<`UnconfirmedDeactivateArtifact`\>\> --- ### depositArtifact ▸ **depositArtifact**(`locationId`, `artifactId`): `Promise`<`Transaction`<`UnconfirmedDepositArtifact`\>\> Submits a transaction to the blockchain to deposit an artifact on a given planet. You must own the planet and you must own the artifact directly (can't be locked in contract) #### Parameters | Name | Type | | :----------- | :----------- | | `locationId` | `LocationId` | | `artifactId` | `ArtifactId` | #### Returns `Promise`<`Transaction`<`UnconfirmedDepositArtifact`\>\> --- ### destroy ▸ **destroy**(): `void` #### Returns `void` --- ### findArtifact ▸ **findArtifact**(`planetId`, `bypassChecks?`): `Promise`<`Transaction`<`UnconfirmedFindArtifact`\>\> Calls the contract to find an artifact on the given planet. #### Parameters | Name | Type | Default value | | :------------- | :----------- | :------------ | | `planetId` | `LocationId` | `undefined` | | `bypassChecks` | `boolean` | `false` | #### Returns `Promise`<`Transaction`<`UnconfirmedFindArtifact`\>\> --- ### findRandomHomePlanet ▸ `Private` **findRandomHomePlanet**(): `Promise`<`LocatablePlanet`\> #### Returns `Promise`<`LocatablePlanet`\> --- ### forceTick ▸ **forceTick**(`locationId`): `void` #### Parameters | Name | Type | | :----------- | :----------- | | `locationId` | `LocationId` | #### Returns `void` --- ### getAccount ▸ **getAccount**(): `undefined` \| `EthAddress` Gets the address of the player logged into this game manager. #### Returns `undefined` \| `EthAddress` --- ### getActiveArtifact ▸ **getActiveArtifact**(`planet`): `undefined` \| `Artifact` Gets the active artifact on this planet, if one exists. #### Parameters | Name | Type | | :------- | :------- | | `planet` | `Planet` | #### Returns `undefined` \| `Artifact` --- ### getAddress ▸ **getAddress**(): `undefined` \| `EthAddress` #### Returns `undefined` \| `EthAddress` --- ### getAllOwnedPlanets ▸ **getAllOwnedPlanets**(): `Planet`[] Gets a list of planets that have an owner. #### Returns `Planet`[] --- ### getAllPlanets ▸ **getAllPlanets**(): `Iterable`<`Planet`\> Gets all planets. This means all planets that are in the contract, and also all planets that have been mined locally. Does not update planets if they are stale. NOT PERFORMANT - for scripting only. #### Returns `Iterable`<`Planet`\> --- ### getAllPlayers ▸ **getAllPlayers**(): `Player`[] Gets a list of all the players in the game (not just the ones you've encounterd) #### Returns `Player`[] --- ### getAllVoyages ▸ **getAllVoyages**(): `QueuedArrival`[] Gets all voyages that have not completed. #### Returns `QueuedArrival`[] --- ### getArtifactMap ▸ **getArtifactMap**(): `Map`<`ArtifactId`, `Artifact`\> Return a reference to the artifact map #### Returns `Map`<`ArtifactId`, `Artifact`\> --- ### getArtifactUpdated$ ▸ **getArtifactUpdated$**(): `Monomitter`<`ArtifactId`\> #### Returns `Monomitter`<`ArtifactId`\> --- ### getArtifactWithId ▸ **getArtifactWithId**(`artifactId?`): `undefined` \| `Artifact` Gets the artifact with the given id. Null if no artifact with id exists. #### Parameters | Name | Type | | :------------ | :----------- | | `artifactId?` | `ArtifactId` | #### Returns `undefined` \| `Artifact` --- ### getArtifactsWithIds ▸ **getArtifactsWithIds**(`artifactIds?`): (`undefined` \| `Artifact`)[] Gets the artifacts with the given ids, including ones we know exist but haven't been loaded, represented by `undefined`. #### Parameters | Name | Type | Default value | | :------------ | :------------- | :------------ | | `artifactIds` | `ArtifactId`[] | `[]` | #### Returns (`undefined` \| `Artifact`)[] --- ### getCaptureZoneGenerator ▸ **getCaptureZoneGenerator**(): `undefined` \| [`CaptureZoneGenerator`](Backend_GameLogic_CaptureZoneGenerator.CaptureZoneGenerator.md) #### Returns `undefined` \| [`CaptureZoneGenerator`](Backend_GameLogic_CaptureZoneGenerator.CaptureZoneGenerator.md) --- ### getCaptureZones ▸ **getCaptureZones**(): `Set`<`CaptureZone`\> #### Returns `Set`<`CaptureZone`\> --- ### getChunk ▸ **getChunk**(`chunkFootprint`): `undefined` \| `Chunk` #### Parameters | Name | Type | | :--------------- | :---------- | | `chunkFootprint` | `Rectangle` | #### Returns `undefined` \| `Chunk` --- ### getChunkStore ▸ **getChunkStore**(): [`default`](Backend_Storage_PersistentChunkStore.default.md) #### Returns [`default`](Backend_Storage_PersistentChunkStore.default.md) --- ### getClaimedLocations ▸ **getClaimedLocations**(): `Map`<`LocationId`, `ClaimedLocation`\> Gets a map of all location IDs which have been claimed. #### Returns `Map`<`LocationId`, `ClaimedLocation`\> --- ### getConstructors ▸ **getConstructors**(): `Object` Returns constructors of classes that may be useful for developing plugins. #### Returns `Object` | Name | Type | | :----------------------- | :---------------------------------------------------------------------------------------- | | `MinerManager` | typeof [`default`](Backend_Miner_MinerManager.default.md) | | `SpiralPattern` | typeof [`SpiralPattern`](Backend_Miner_MiningPatterns.SpiralPattern.md) | | `SwissCheesePattern` | typeof [`SwissCheesePattern`](Backend_Miner_MiningPatterns.SwissCheesePattern.md) | | `TowardsCenterPattern` | typeof [`TowardsCenterPattern`](Backend_Miner_MiningPatterns.TowardsCenterPattern.md) | | `TowardsCenterPatternV2` | typeof [`TowardsCenterPatternV2`](Backend_Miner_MiningPatterns.TowardsCenterPatternV2.md) | --- ### getContract ▸ **getContract**(): `DarkForest` #### Returns `DarkForest` --- ### getContractAPI ▸ **getContractAPI**(): [`ContractsAPI`](Backend_GameLogic_ContractsAPI.ContractsAPI.md) Get the thing that handles contract interaction. #### Returns [`ContractsAPI`](Backend_GameLogic_ContractsAPI.ContractsAPI.md) --- ### getContractAddress ▸ **getContractAddress**(): `EthAddress` Gets the address of the `DarkForest` contract, which is the 'backend' of the game. #### Returns `EthAddress` --- ### getContractConstants ▸ **getContractConstants**(): [`ContractConstants`](../interfaces/types_darkforest_api_ContractsAPITypes.ContractConstants.md) #### Returns [`ContractConstants`](../interfaces/types_darkforest_api_ContractsAPITypes.ContractConstants.md) --- ### getCurrentlyExploringChunk ▸ **getCurrentlyExploringChunk**(): `undefined` \| `Rectangle` Gets the rectangle bounding the chunk that the miner is currently in the process of hashing. #### Returns `undefined` \| `Rectangle` --- ### getDefaultSpaceJunkForPlanetLevel ▸ **getDefaultSpaceJunkForPlanetLevel**(`level`): `number` #### Parameters | Name | Type | | :------ | :------- | | `level` | `number` | #### Returns `number` --- ### getDiagnostics ▸ **getDiagnostics**(): `Diagnostics` Gets some diagnostic information about the game. Returns a copy, you can't modify it. #### Returns `Diagnostics` --- ### getDist ▸ **getDist**(`fromId`, `toId`): `number` Gets the distance between two planets. Throws an exception if you don't know the location of either planet. Takes into account wormholes. #### Parameters | Name | Type | | :------- | :----------- | | `fromId` | `LocationId` | | `toId` | `LocationId` | #### Returns `number` --- ### getDistCoords ▸ **getDistCoords**(`fromCoords`, `toCoords`): `number` Gets the distance between two coordinates in space. #### Parameters | Name | Type | | :----------- | :------------ | | `fromCoords` | `WorldCoords` | | `toCoords` | `WorldCoords` | #### Returns `number` --- ### getEndTimeSeconds ▸ **getEndTimeSeconds**(): `number` The game ends at a particular time in the future - get this time measured in seconds from the epoch. #### Returns `number` --- ### getEnergyArrivingForMove ▸ **getEnergyArrivingForMove**(`fromId`, `toId`, `distance`, `sentEnergy`, `abandoning`): `number` Gets the amount of energy that would arrive if a voyage with the given parameters was to occur. The toPlanet is optional, in case you want an estimate that doesn't include wormhole speedups. #### Parameters | Name | Type | | :----------- | :-------------------------- | | `fromId` | `LocationId` | | `toId` | `undefined` \| `LocationId` | | `distance` | `undefined` \| `number` | | `sentEnergy` | `number` | | `abandoning` | `boolean` | #### Returns `number` --- ### getEnergyCurveAtPercent ▸ **getEnergyCurveAtPercent**(`planet`, `percent`): `number` returns timestamp (seconds) that planet will reach percent% of energycap time may be in the past #### Parameters | Name | Type | | :-------- | :------- | | `planet` | `Planet` | | `percent` | `number` | #### Returns `number` --- ### getEnergyNeededForMove ▸ **getEnergyNeededForMove**(`fromId`, `toId`, `arrivingEnergy`, `abandoning?`): `number` Gets the amount of energy needed in order for a voyage from the given to the given planet to arrive with your desired amount of energy. #### Parameters | Name | Type | Default value | | :--------------- | :----------- | :------------ | | `fromId` | `LocationId` | `undefined` | | `toId` | `LocationId` | `undefined` | | `arrivingEnergy` | `number` | `undefined` | | `abandoning` | `boolean` | `false` | #### Returns `number` --- ### getEnergyOfPlayer ▸ **getEnergyOfPlayer**(`player`): `number` Gets the total amount of energy that lives on planets that the given player owns. #### Parameters | Name | Type | | :------- | :----------- | | `player` | `EthAddress` | #### Returns `number` --- ### getEthConnection ▸ **getEthConnection**(): `EthConnection` #### Returns `EthConnection` --- ### getExploredChunks ▸ **getExploredChunks**(): `Iterable`<`Chunk`\> Gets all the map chunks that this client is aware of. Chunks may have come from mining, or from importing map data. #### Returns `Iterable`<`Chunk`\> --- ### getGameObjects ▸ **getGameObjects**(): [`GameObjects`](Backend_GameLogic_GameObjects.GameObjects.md) Gets a reference to the game's internal representation of the world state. This includes voyages, planets, artifacts, and active wormholes, #### Returns [`GameObjects`](Backend_GameLogic_GameObjects.GameObjects.md) --- ### getHashConfig ▸ **getHashConfig**(): [`HashConfig`](../modules/types_global_GlobalTypes.md#hashconfig) Gets the HASH CONFIG #### Returns [`HashConfig`](../modules/types_global_GlobalTypes.md#hashconfig) --- ### getHashesPerSec ▸ **getHashesPerSec**(): `number` Gets the amount of hashes per second that the miner manager is calculating. #### Returns `number` --- ### getHomeCoords ▸ **getHomeCoords**(): `undefined` \| `WorldCoords` Gets the location of your home planet. #### Returns `undefined` \| `WorldCoords` --- ### getHomeHash ▸ **getHomeHash**(): `undefined` \| `LocationId` Gets the hash of the location of your home planet. #### Returns `undefined` \| `LocationId` --- ### getLocationOfPlanet ▸ **getLocationOfPlanet**(`planetId`): `undefined` \| `WorldLocation` Gets the location of the given planet. Returns undefined if the planet does not exist, or if we do not know the location of this planet NOT update the planet if the planet is stale, which means this function is fast. #### Parameters | Name | Type | | :--------- | :----------- | | `planetId` | `LocationId` | #### Returns `undefined` \| `WorldLocation` --- ### getMaxMoveDist ▸ **getMaxMoveDist**(`planetId`, `sendingPercent`, `abandoning`): `number` Gets the maximuim distance that you can send your energy from the given planet, using the given percentage of that planet's current silver. #### Parameters | Name | Type | | :--------------- | :----------- | | `planetId` | `LocationId` | | `sendingPercent` | `number` | | `abandoning` | `boolean` | #### Returns `number` --- ### getMiningPattern ▸ **getMiningPattern**(): `undefined` \| [`MiningPattern`](../interfaces/Backend_Miner_MiningPatterns.MiningPattern.md) Gets the mining pattern that the miner is currently using. #### Returns `undefined` \| [`MiningPattern`](../interfaces/Backend_Miner_MiningPatterns.MiningPattern.md) --- ### getMyArtifactMap ▸ **getMyArtifactMap**(): `Map`<`ArtifactId`, `Artifact`\> Return a reference to the map of my artifacts #### Returns `Map`<`ArtifactId`, `Artifact`\> --- ### getMyArtifacts ▸ **getMyArtifacts**(): `Artifact`[] gets both deposited artifacts that are on planets i own as well as artifacts i own #### Returns `Artifact`[] --- ### getMyArtifactsUpdated$ ▸ **getMyArtifactsUpdated$**(): `Monomitter`<`Map`<`ArtifactId`, `Artifact`\>\> #### Returns `Monomitter`<`Map`<`ArtifactId`, `Artifact`\>\> --- ### getMyBalance ▸ **getMyBalance**(): `BigNumber` Gets the balance of the account #### Returns `BigNumber` --- ### getMyBalance$ ▸ **getMyBalance$**(): `Monomitter`<`BigNumber`\> Returns the monomitter which publishes events whenever the player's balance changes. #### Returns `Monomitter`<`BigNumber`\> --- ### getMyBalanceEth ▸ **getMyBalanceEth**(): `number` Gets the balance of the account measured in Eth (i.e. in full units of the chain). #### Returns `number` --- ### getMyPlanetMap ▸ **getMyPlanetMap**(): `Map`<`LocationId`, `Planet`\> Return a reference to the map of my planets #### Returns `Map`<`LocationId`, `Planet`\> --- ### getMyPlanets ▸ **getMyPlanets**(): `Planet`[] Gets a list of the planets that the player logged into this `GameManager` owns. #### Returns `Planet`[] --- ### getMyPlanetsUpdated$ ▸ **getMyPlanetsUpdated$**(): `Monomitter`<`Map`<`LocationId`, `Planet`\>\> #### Returns `Monomitter`<`Map`<`LocationId`, `Planet`\>\> --- ### getMyScore ▸ **getMyScore**(): `undefined` \| `number` Get the score of the currently logged-in account. #### Returns `undefined` \| `number` --- ### getNextBroadcastAvailableTimestamp ▸ **getNextBroadcastAvailableTimestamp**(): `number` Gets the timestamp (ms) of the next time that we can broadcast the coordinates of a planet. #### Returns `number` --- ### getNextClaimAvailableTimestamp ▸ **getNextClaimAvailableTimestamp**(): `number` Gets the timestamp (ms) of the next time that we can claim a planet. #### Returns `number` --- ### getNextRevealCountdownInfo ▸ **getNextRevealCountdownInfo**(): [`RevealCountdownInfo`](../interfaces/types_global_GlobalTypes.RevealCountdownInfo.md) Returns info about the next time you can broadcast coordinates #### Returns [`RevealCountdownInfo`](../interfaces/types_global_GlobalTypes.RevealCountdownInfo.md) --- ### getNotificationsManager ▸ **getNotificationsManager**(): [`default`](Frontend_Game_NotificationManager.default.md) #### Returns [`default`](Frontend_Game_NotificationManager.default.md) --- ### getPaused ▸ **getPaused**(): `boolean` #### Returns `boolean` --- ### getPaused$ ▸ **getPaused$**(): `Monomitter`<`boolean`\> #### Returns `Monomitter`<`boolean`\> --- ### getPerlinThresholds ▸ **getPerlinThresholds**(): [`number`, `number`, `number`] The perlin value at each coordinate determines the space type. There are four space types, which means there are four ranges on the number line that correspond to each space type. This function returns the boundary values between each of these four ranges: `PERLIN_THRESHOLD_1`, `PERLIN_THRESHOLD_2`, `PERLIN_THRESHOLD_3`. #### Returns [`number`, `number`, `number`] --- ### getPlanetLevel ▸ **getPlanetLevel**(`planetId`): `undefined` \| `PlanetLevel` Gets the level of the given planet. Returns undefined if the planet does not exist. Does NOT update the planet if the planet is stale, which means this function is fast. #### Parameters | Name | Type | | :--------- | :----------- | | `planetId` | `LocationId` | #### Returns `undefined` \| `PlanetLevel` --- ### getPlanetMap ▸ **getPlanetMap**(): `Map`<`LocationId`, `Planet`\> Return a reference to the planet map #### Returns `Map`<`LocationId`, `Planet`\> --- ### getPlanetRarity ▸ **getPlanetRarity**(): `number` Gets the rarity of planets in the universe #### Returns `number` --- ### getPlanetUpdated$ ▸ **getPlanetUpdated$**(): `Monomitter`<`LocationId`\> #### Returns `Monomitter`<`LocationId`\> --- ### getPlanetWithCoords ▸ **getPlanetWithCoords**(`coords`): `undefined` \| `LocatablePlanet` Gets the planet that is located at the given coordinates. Returns undefined if not a valid location or if no planet exists at location. If the planet needs to be updated (because some time has passed since we last updated the planet), then updates that planet first. #### Parameters | Name | Type | | :------- | :------------ | | `coords` | `WorldCoords` | #### Returns `undefined` \| `LocatablePlanet` --- ### getPlanetWithId ▸ **getPlanetWithId**(`planetId`): `undefined` \| `Planet` Gets the planet with the given hash. Returns undefined if the planet is neither in the contract nor has been discovered locally. If the planet needs to be updated (because some time has passed since we last updated the planet), then updates that planet first. #### Parameters | Name | Type | | :--------- | :-------------------------- | | `planetId` | `undefined` \| `LocationId` | #### Returns `undefined` \| `Planet` --- ### getPlanetsInRange ▸ **getPlanetsInRange**(`planetId`, `sendingPercent`, `abandoning`): `Planet`[] Gets all the planets that you can reach with at least 1 energy from the given planet. Does not take into account wormholes. #### Parameters | Name | Type | | :--------------- | :----------- | | `planetId` | `LocationId` | | `sendingPercent` | `number` | | `abandoning` | `boolean` | #### Returns `Planet`[] --- ### getPlanetsInWorldRectangle ▸ **getPlanetsInWorldRectangle**(`worldX`, `worldY`, `worldWidth`, `worldHeight`, `levels`, `planetLevelToRadii`, `updateIfStale?`): `LocatablePlanet`[] Gets the ids of all the planets that are both within the given bounding box (defined by its bottom left coordinate, width, and height) in the world and of a level that was passed in via the `planetLevels` parameter. #### Parameters | Name | Type | Default value | | :------------------- | :------------------------ | :------------ | | `worldX` | `number` | `undefined` | | `worldY` | `number` | `undefined` | | `worldWidth` | `number` | `undefined` | | `worldHeight` | `number` | `undefined` | | `levels` | `number`[] | `undefined` | | `planetLevelToRadii` | `Map`<`number`, `Radii`\> | `undefined` | | `updateIfStale` | `boolean` | `true` | #### Returns `LocatablePlanet`[] --- ### getPlanetsWithIds ▸ **getPlanetsWithIds**(`planetId`): `Planet`[] Gets a list of planets in the client's memory with the given ids. If a planet with the given id doesn't exist, no entry for that planet will be returned in the result. #### Parameters | Name | Type | | :--------- | :------------- | | `planetId` | `LocationId`[] | #### Returns `Planet`[] --- ### getPlayer ▸ **getPlayer**(`address?`): `undefined` \| `Player` Gets either the given player, or if no address was provided, gets the player that is logged this client. #### Parameters | Name | Type | | :--------- | :----------- | | `address?` | `EthAddress` | #### Returns `undefined` \| `Player` --- ### getPlayerScore ▸ **getPlayerScore**(`addr`): `undefined` \| `number` #### Parameters | Name | Type | | :----- | :----------- | | `addr` | `EthAddress` | #### Returns `undefined` \| `number` --- ### getPlayerSpaceJunk ▸ **getPlayerSpaceJunk**(`addr`): `undefined` \| `number` #### Parameters | Name | Type | | :----- | :----------- | | `addr` | `EthAddress` | #### Returns `undefined` \| `number` --- ### getPlayerSpaceJunkLimit ▸ **getPlayerSpaceJunkLimit**(`addr`): `undefined` \| `number` #### Parameters | Name | Type | | :----- | :----------- | | `addr` | `EthAddress` | #### Returns `undefined` \| `number` --- ### getPrivateKey ▸ **getPrivateKey**(): `undefined` \| `string` Gets the private key of the burner wallet used by this account. #### Returns `undefined` \| `string` --- ### getRangeBuff ▸ **getRangeBuff**(`abandoning`): `number` #### Parameters | Name | Type | | :----------- | :-------- | | `abandoning` | `boolean` | #### Returns `number` --- ### getRevealedLocations ▸ **getRevealedLocations**(): `Map`<`LocationId`, `RevealedLocation`\> Gets a map of all location IDs whose coords have been publically revealed #### Returns `Map`<`LocationId`, `RevealedLocation`\> --- ### getSafeMode ▸ **getSafeMode**(): `boolean` #### Returns `boolean` --- ### getSignedTwitter ▸ **getSignedTwitter**(`twitter`): `Promise`<`string`\> Signs the given twitter handle with the private key of the current user. Used to verify that the person who owns the Dark Forest account was the one that attempted to link a twitter to their account. #### Parameters | Name | Type | | :-------- | :------- | | `twitter` | `string` | #### Returns `Promise`<`string`\> --- ### getSilverCurveAtPercent ▸ **getSilverCurveAtPercent**(`planet`, `percent`): `undefined` \| `number` returns timestamp (seconds) that planet will reach percent% of silcap if doesn't produce silver, returns undefined if already over percent% of silcap, #### Parameters | Name | Type | | :-------- | :------- | | `planet` | `Planet` | | `percent` | `number` | #### Returns `undefined` \| `number` --- ### getSilverOfPlayer ▸ **getSilverOfPlayer**(`player`): `number` Gets the total amount of silver that lives on planets that the given player owns. #### Parameters | Name | Type | | :------- | :----------- | | `player` | `EthAddress` | #### Returns `number` --- ### getSnarkHelper ▸ **getSnarkHelper**(): [`default`](Backend_Utils_SnarkArgsHelper.default.md) #### Returns [`default`](Backend_Utils_SnarkArgsHelper.default.md) --- ### getSpaceships ▸ `Private` **getSpaceships**(): `Promise`<`void`\> #### Returns `Promise`<`void`\> --- ### getSpeedBuff ▸ **getSpeedBuff**(`abandoning`): `number` Right now the only buffs supported in this way are speed/range buffs from Abandoning a planet. The abandoning argument is used when interacting with this function programmatically. #### Parameters | Name | Type | | :----------- | :-------- | | `abandoning` | `boolean` | #### Returns `number` --- ### getStalePlanetWithId ▸ **getStalePlanetWithId**(`planetId`): `undefined` \| `Planet` #### Parameters | Name | Type | | :--------- | :----------- | | `planetId` | `LocationId` | #### Returns `undefined` \| `Planet` --- ### getTemperature ▸ **getTemperature**(`coords`): `number` Gets the temperature of a given location. #### Parameters | Name | Type | | :------- | :------------ | | `coords` | `WorldCoords` | #### Returns `number` --- ### getTimeForMove ▸ **getTimeForMove**(`fromId`, `toId`, `abandoning?`): `number` Gets the amount of time, in seconds that a voyage between from the first to the second planet would take. #### Parameters | Name | Type | Default value | | :----------- | :----------- | :------------ | | `fromId` | `LocationId` | `undefined` | | `toId` | `LocationId` | `undefined` | | `abandoning` | `boolean` | `false` | #### Returns `number` --- ### getTokenMintEndTimeSeconds ▸ **getTokenMintEndTimeSeconds**(): `number` Dark Forest tokens can only be minted up to a certain time - get this time measured in seconds from epoch. #### Returns `number` --- ### getTwitter ▸ **getTwitter**(`address`): `undefined` \| `string` Gets the twitter handle of the given ethereum account which is associated with Dark Forest. #### Parameters | Name | Type | | :-------- | :-------------------------- | | `address` | `undefined` \| `EthAddress` | #### Returns `undefined` \| `string` --- ### getUIEventEmitter ▸ **getUIEventEmitter**(): [`default`](Frontend_Utils_UIEmitter.default.md) Helpful for listening to user input events. #### Returns [`default`](Frontend_Utils_UIEmitter.default.md) --- ### getUnconfirmedMoves ▸ **getUnconfirmedMoves**(): `Transaction`<`UnconfirmedMove`\>[] Gets all moves that this client has queued to be uploaded to the contract, but have not been successfully confirmed yet. #### Returns `Transaction`<`UnconfirmedMove`\>[] --- ### getUnconfirmedUpgrades ▸ **getUnconfirmedUpgrades**(): `Transaction`<`UnconfirmedUpgrade`\>[] Gets all upgrades that this client has queued to be uploaded to the contract, but have not been successfully confirmed yet. #### Returns `Transaction`<`UnconfirmedUpgrade`\>[] --- ### getUnconfirmedWormholeActivations ▸ **getUnconfirmedWormholeActivations**(): `Transaction`<`UnconfirmedActivateArtifact`\>[] #### Returns `Transaction`<`UnconfirmedActivateArtifact`\>[] --- ### getUniverseTotalEnergy ▸ **getUniverseTotalEnergy**(): `number` Gets the total amount of energy that lives on a planet that somebody owns. #### Returns `number` --- ### getUpgrade ▸ **getUpgrade**(`branch`, `level`): `Upgrade` Returns the upgrade that would be applied to a planet given a particular upgrade branch (defense, range, speed) and level of upgrade. #### Parameters | Name | Type | | :------- | :------- | | `branch` | `number` | | `level` | `number` | #### Returns `Upgrade` --- ### getWorldRadius ▸ **getWorldRadius**(): `number` Gets the radius of the playable area of the universe. #### Returns `number` --- ### getWorldSilver ▸ **getWorldSilver**(): `number` Gets the total amount of silver that lives on a planet that somebody owns. #### Returns `number` --- ### getWormholeFactors ▸ **getWormholeFactors**(`fromPlanet`, `toPlanet`): `undefined` \| { `distanceFactor`: `number` ; `speedFactor`: `number` } If there's an active artifact on either of these planets which happens to be a wormhole which is active and targetting the other planet, return the wormhole boost which is greater. Values represent a multiplier. #### Parameters | Name | Type | | :----------- | :------- | | `fromPlanet` | `Planet` | | `toPlanet` | `Planet` | #### Returns `undefined` \| { `distanceFactor`: `number` ; `speedFactor`: `number` } --- ### getWormholes ▸ **getWormholes**(): `Iterable`<`Wormhole`\> #### Returns `Iterable`<`Wormhole`\> --- ### hardRefreshArtifact ▸ **hardRefreshArtifact**(`artifactId`): `Promise`<`void`\> #### Parameters | Name | Type | | :----------- | :----------- | | `artifactId` | `ArtifactId` | #### Returns `Promise`<`void`\> --- ### hardRefreshPlanet ▸ **hardRefreshPlanet**(`planetId`): `Promise`<`void`\> #### Parameters | Name | Type | | :--------- | :----------- | | `planetId` | `LocationId` | #### Returns `Promise`<`void`\> --- ### hardRefreshPlayer ▸ `Private` **hardRefreshPlayer**(`address?`): `Promise`<`void`\> #### Parameters | Name | Type | | :--------- | :----------- | | `address?` | `EthAddress` | #### Returns `Promise`<`void`\> --- ### hasJoinedGame ▸ **hasJoinedGame**(): `boolean` Whether or not this client has successfully found and landed on a home planet. #### Returns `boolean` --- ### hasMinedChunk ▸ **hasMinedChunk**(`chunkLocation`): `boolean` Whether or not the given rectangle has been mined. #### Parameters | Name | Type | | :-------------- | :---------- | | `chunkLocation` | `Rectangle` | #### Returns `boolean` --- ### initMiningManager ▸ `Private` **initMiningManager**(`homeCoords`, `cores?`): `void` #### Parameters | Name | Type | | :----------- | :------------ | | `homeCoords` | `WorldCoords` | | `cores?` | `number` | #### Returns `void` --- ### invadePlanet ▸ **invadePlanet**(`locationId`): `Promise`<`Transaction`<`UnconfirmedInvadePlanet`\>\> #### Parameters | Name | Type | | :----------- | :----------- | | `locationId` | `LocationId` | #### Returns `Promise`<`Transaction`<`UnconfirmedInvadePlanet`\>\> --- ### isAdmin ▸ **isAdmin**(): `boolean` #### Returns `boolean` --- ### isMining ▸ **isMining**(): `boolean` Whether or not the miner is currently exploring space. #### Returns `boolean` --- ### isPlanetMineable ▸ **isPlanetMineable**(`p`): `boolean` Whether or not the given planet is capable of minting an artifact. #### Parameters | Name | Type | | :--- | :------- | | `p` | `Planet` | #### Returns `boolean` --- ### isRoundOver ▸ **isRoundOver**(): `boolean` Returns whether or not the current round has ended. #### Returns `boolean` --- ### joinGame ▸ **joinGame**(`beforeRetry`): `Promise`<`void`\> Attempts to join the game. Should not be called once you've already joined. #### Parameters | Name | Type | | :------------ | :-------------------------------------- | | `beforeRetry` | (`e`: `Error`) => `Promise`<`boolean`\> | #### Returns `Promise`<`void`\> --- ### listenForNewBlock ▸ **listenForNewBlock**(): `void` #### Returns `void` --- ### loadContract ▸ **loadContract**<`T`\>(`contractAddress`, `contractABI`): `Promise`<`T`\> Returns an instance of a `Contract` from the ethersjs library. This is the library we use to connect to the blockchain. For documentation about how `Contract` works, see: https://docs.ethers.io/v5/api/contract/contract/ Also, registers your contract in the system to make calls against it and to reload it when necessary (such as the RPC endpoint changing). #### Type parameters | Name | Type | | :--- | :----------------------- | | `T` | extends `Contract`<`T`\> | #### Parameters | Name | Type | | :---------------- | :------------------ | | `contractAddress` | `string` | | `contractABI` | `ContractInterface` | #### Returns `Promise`<`T`\> --- ### loadPlugins ▸ **loadPlugins**(): `Promise`<[`SerializedPlugin`](../interfaces/Backend_Plugins_SerializedPlugin.SerializedPlugin.md)[]\> Load the serialized versions of all the plugins that this player has. #### Returns `Promise`<[`SerializedPlugin`](../interfaces/Backend_Plugins_SerializedPlugin.SerializedPlugin.md)[]\> --- ### locationBigIntFromCoords ▸ **locationBigIntFromCoords**(`coords`): `BigInteger` #### Parameters | Name | Type | | :------- | :------------ | | `coords` | `WorldCoords` | #### Returns `BigInteger` --- ### locationFromCoords ▸ `Private` **locationFromCoords**(`coords`): `WorldLocation` computes the WorldLocation object corresponding to a set of coordinates very slow since it actually calculates the hash; do not use in render loop #### Parameters | Name | Type | | :------- | :------------ | | `coords` | `WorldCoords` | #### Returns `WorldLocation` --- ### move ▸ **move**(`from`, `to`, `forces`, `silver`, `artifactMoved?`, `abandoning?`, `bypassChecks?`): `Promise`<`Transaction`<`UnconfirmedMove`\>\> Submits a transaction to the blockchain to move the given amount of resources from the given planet to the given planet. #### Parameters | Name | Type | Default value | | :--------------- | :----------- | :------------ | | `from` | `LocationId` | `undefined` | | `to` | `LocationId` | `undefined` | | `forces` | `number` | `undefined` | | `silver` | `number` | `undefined` | | `artifactMoved?` | `ArtifactId` | `undefined` | | `abandoning` | `boolean` | `false` | | `bypassChecks` | `boolean` | `false` | #### Returns `Promise`<`Transaction`<`UnconfirmedMove`\>\> --- ### onTxCancelled ▸ `Private` **onTxCancelled**(`tx`): `void` #### Parameters | Name | Type | | :--- | :------------------------- | | `tx` | `Transaction`<`TxIntent`\> | #### Returns `void` --- ### onTxConfirmed ▸ `Private` **onTxConfirmed**(`tx`): `void` #### Parameters | Name | Type | | :--- | :------------------------- | | `tx` | `Transaction`<`TxIntent`\> | #### Returns `void` --- ### onTxReverted ▸ `Private` **onTxReverted**(`tx`): `void` #### Parameters | Name | Type | | :--- | :------------------------- | | `tx` | `Transaction`<`TxIntent`\> | #### Returns `void` --- ### onTxSubmit ▸ `Private` **onTxSubmit**(`tx`): `void` #### Parameters | Name | Type | | :--- | :------------------------- | | `tx` | `Transaction`<`TxIntent`\> | #### Returns `void` --- ### prospectPlanet ▸ **prospectPlanet**(`planetId`, `bypassChecks?`): `Promise`<`Transaction`<`UnconfirmedProspectPlanet`\>\> #### Parameters | Name | Type | Default value | | :------------- | :----------- | :------------ | | `planetId` | `LocationId` | `undefined` | | `bypassChecks` | `boolean` | `false` | #### Returns `Promise`<`Transaction`<`UnconfirmedProspectPlanet`\>\> --- ### refreshNetworkHealth ▸ `Private` **refreshNetworkHealth**(): `Promise`<`void`\> #### Returns `Promise`<`void`\> --- ### refreshScoreboard ▸ `Private` **refreshScoreboard**(): `Promise`<`void`\> #### Returns `Promise`<`void`\> --- ### refreshServerPlanetStates ▸ **refreshServerPlanetStates**(`planetIds`): `Promise`<`void`\> We have two locations which planet state can live: on the server, and on the blockchain. We use the blockchain for the 'physics' of the universe, and the webserver for optional 'add-on' features, which are cryptographically secure, but live off-chain. This function loads the planet states which live on the server. Plays nicely with our notifications system and sets the appropriate loading state values on the planet. #### Parameters | Name | Type | | :---------- | :------------- | | `planetIds` | `LocationId`[] | #### Returns `Promise`<`void`\> --- ### refreshTwitters ▸ `Private` **refreshTwitters**(): `Promise`<`void`\> #### Returns `Promise`<`void`\> --- ### revealLocation ▸ **revealLocation**(`planetId`): `Promise`<`Transaction`<`UnconfirmedReveal`\>\> Reveals a planet's location on-chain. #### Parameters | Name | Type | | :--------- | :----------- | | `planetId` | `LocationId` | #### Returns `Promise`<`Transaction`<`UnconfirmedReveal`\>\> --- ### savePlugins ▸ **savePlugins**(`savedPlugins`): `Promise`<`void`\> Overwrites all the saved plugins to equal the given array of plugins. #### Parameters | Name | Type | | :------------- | :----------------------------------------------------------------------------------------- | | `savedPlugins` | [`SerializedPlugin`](../interfaces/Backend_Plugins_SerializedPlugin.SerializedPlugin.md)[] | #### Returns `Promise`<`void`\> --- ### setMinerCores ▸ **setMinerCores**(`nCores`): `void` Set the amount of cores to mine the universe with. More cores equals faster! #### Parameters | Name | Type | | :------- | :------- | | `nCores` | `number` | #### Returns `void` --- ### setMiningPattern ▸ **setMiningPattern**(`pattern`): `void` Sets the mining pattern of the miner. This kills the old miner and starts this one. #### Parameters | Name | Type | | :-------- | :----------------------------------------------------------------------------- | | `pattern` | [`MiningPattern`](../interfaces/Backend_Miner_MiningPatterns.MiningPattern.md) | #### Returns `void` --- ### setPlanetEmoji ▸ **setPlanetEmoji**(`locationId`, `emojiStr`): `Promise`<`void`\> If you are the owner of this planet, you can set an 'emoji' to hover above the planet. `emojiStr` must be a string that contains a single emoji, otherwise this function will throw an error. The emoji is stored off-chain in a postgres database. We verify planet ownership via a contract call from the webserver, and by verifying that the request to add (or remove) an emoji from a planet was signed by the owner. #### Parameters | Name | Type | | :----------- | :----------- | | `locationId` | `LocationId` | | `emojiStr` | `string` | #### Returns `Promise`<`void`\> --- ### setPlayerTwitters ▸ `Private` **setPlayerTwitters**(`twitters`): `void` #### Parameters | Name | Type | | :--------- | :------------------------------------------------------------------------------------------------ | | `twitters` | [`AddressTwitterMap`](../modules/types_darkforest_api_UtilityServerAPITypes.md#addresstwittermap) | #### Returns `void` --- ### setRadius ▸ `Private` **setRadius**(`worldRadius`): `void` #### Parameters | Name | Type | | :------------ | :------- | | `worldRadius` | `number` | #### Returns `void` --- ### setSafeMode ▸ **setSafeMode**(`safeMode`): `void` #### Parameters | Name | Type | | :--------- | :-------- | | `safeMode` | `boolean` | #### Returns `void` --- ### setSnarkCacheSize ▸ **setSnarkCacheSize**(`size`): `void` Changes the amount of move snark proofs that are cached. #### Parameters | Name | Type | | :----- | :------- | | `size` | `number` | #### Returns `void` --- ### softRefreshPlanet ▸ `Private` **softRefreshPlanet**(`planetId`): `Promise`<`void`\> #### Parameters | Name | Type | | :--------- | :----------- | | `planetId` | `LocationId` | #### Returns `Promise`<`void`\> --- ### spaceTypeFromPerlin ▸ **spaceTypeFromPerlin**(`perlin`): `SpaceType` Each coordinate lives in a particular type of space, determined by a smooth random function called 'perlin noise. #### Parameters | Name | Type | | :------- | :------- | | `perlin` | `number` | #### Returns `SpaceType` --- ### spaceTypePerlin ▸ **spaceTypePerlin**(`coords`, `floor`): `number` Gets the perlin value at the given location in the world. SpaceType is based on this value. #### Parameters | Name | Type | | :------- | :------------ | | `coords` | `WorldCoords` | | `floor` | `boolean` | #### Returns `number` --- ### startExplore ▸ **startExplore**(): `void` Starts the miner. #### Returns `void` --- ### stopExplore ▸ **stopExplore**(): `void` Stops the miner. #### Returns `void` --- ### submitDisconnectTwitter ▸ **submitDisconnectTwitter**(`twitter`): `Promise`<`void`\> #### Parameters | Name | Type | | :-------- | :------- | | `twitter` | `string` | #### Returns `Promise`<`void`\> --- ### submitPlanetMessage ▸ `Private` **submitPlanetMessage**(`locationId`, `type`, `body`): `Promise`<`void`\> The planet emoji feature is built on top of a more general 'Planet Message' system, which allows players to upload pieces of data called 'Message's to planets that they own. Emojis are just one type of message. Their implementation leaves the door open to more off-chain data. #### Parameters | Name | Type | | :----------- | :------------------ | | `locationId` | `LocationId` | | `type` | `PlanetMessageType` | | `body` | `unknown` | #### Returns `Promise`<`void`\> --- ### submitTransaction ▸ **submitTransaction**<`T`\>(`txIntent`, `overrides?`): `Promise`<`Transaction`<`T`\>\> #### Type parameters | Name | Type | | :--- | :----------------- | | `T` | extends `TxIntent` | #### Parameters | Name | Type | | :----------- | :------------------- | | `txIntent` | `T` | | `overrides?` | `TransactionRequest` | #### Returns `Promise`<`Transaction`<`T`\>\> --- ### submitVerifyTwitter ▸ **submitVerifyTwitter**(`twitter`): `Promise`<`boolean`\> Once you have posted the verification tweet - complete the twitter-account-linking process by telling the Dark Forest webserver to look at that tweet. #### Parameters | Name | Type | | :-------- | :------- | | `twitter` | `string` | #### Returns `Promise`<`boolean`\> --- ### testNotification ▸ **testNotification**(): `void` #### Returns `void` --- ### timeUntilNextBroadcastAvailable ▸ **timeUntilNextBroadcastAvailable**(): `number` Gets the amount of time (ms) until the next time the current player can broadcast a planet. #### Returns `number` --- ### transferOwnership ▸ **transferOwnership**(`planetId`, `newOwner`, `bypassChecks?`): `Promise`<`Transaction`<`UnconfirmedPlanetTransfer`\>\> #### Parameters | Name | Type | Default value | | :------------- | :----------- | :------------ | | `planetId` | `LocationId` | `undefined` | | `newOwner` | `EthAddress` | `undefined` | | `bypassChecks` | `boolean` | `false` | #### Returns `Promise`<`Transaction`<`UnconfirmedPlanetTransfer`\>\> --- ### updateDiagnostics ▸ **updateDiagnostics**(`updateFn`): `void` Updates the diagnostic info of the game using the supplied function. Ideally, each spot in the codebase that would like to record a metric is able to update its specific metric in a convenient manner. #### Parameters | Name | Type | | :--------- | :----------------------------- | | `updateFn` | (`d`: `Diagnostics`) => `void` | #### Returns `void` --- ### upgrade ▸ **upgrade**(`planetId`, `branch`, `_bypassChecks?`): `Promise`<`Transaction`<`UnconfirmedUpgrade`\>\> Submits a transaction to the blockchain to upgrade the given planet with the given upgrade branch. You must own the planet, and have enough silver on it to complete the upgrade. #### Parameters | Name | Type | Default value | | :-------------- | :----------- | :------------ | | `planetId` | `LocationId` | `undefined` | | `branch` | `number` | `undefined` | | `_bypassChecks` | `boolean` | `false` | #### Returns `Promise`<`Transaction`<`UnconfirmedUpgrade`\>\> --- ### uploadDiagnostics ▸ `Private` **uploadDiagnostics**(): `Promise`<`void`\> #### Returns `Promise`<`void`\> --- ### verifyMessage ▸ `Private` **verifyMessage**(`message`): `Promise`<`boolean`\> Checks that a message signed by {@link GameManager#signMessage} was signed by the address that it claims it was signed by. #### Parameters | Name | Type | | :-------- | :-------------------------- | | `message` | `SignedMessage`<`unknown`\> | #### Returns `Promise`<`boolean`\> --- ### waitForPlanet ▸ **waitForPlanet**<`T`\>(`locationId`, `predicate`): `Promise`<`T`\> Listen for changes to a planet take action, eg. waitForPlanet("yourAsteroidId", ({current}) => current.silverCap / current.silver > 90) .then(() => { // Send Silver to nearby planet }) #### Type parameters | Name | | :--- | | `T` | #### Parameters | Name | Type | Description | | :----------- | :------------------------------------------------------------------------------------------------------------------ | :---------------------------------------------------------------------------------------------------------- | | `locationId` | `LocationId` | A locationId to watch for updates | | `predicate` | (`__namedParameters`: [`Diff`](../interfaces/Frontend_Utils_EmitterUtils.Diff.md)<`Planet`\>) => `undefined` \| `T` | a function that accepts a Diff and should return a truth-y value, value will be passed to promise.resolve() | #### Returns `Promise`<`T`\> a promise that will resolve with results returned from the predicate function --- ### withdrawArtifact ▸ **withdrawArtifact**(`locationId`, `artifactId`, `bypassChecks?`): `Promise`<`Transaction`<`UnconfirmedWithdrawArtifact`\>\> Withdraws the artifact that is locked up on the given planet. #### Parameters | Name | Type | Default value | | :------------- | :----------- | :------------ | | `locationId` | `LocationId` | `undefined` | | `artifactId` | `ArtifactId` | `undefined` | | `bypassChecks` | `boolean` | `true` | #### Returns `Promise`<`Transaction`<`UnconfirmedWithdrawArtifact`\>\> --- ### withdrawSilver ▸ **withdrawSilver**(`locationId`, `amount`, `bypassChecks?`): `Promise`<`Transaction`<`UnconfirmedWithdrawSilver`\>\> #### Parameters | Name | Type | Default value | | :------------- | :----------- | :------------ | | `locationId` | `LocationId` | `undefined` | | `amount` | `number` | `undefined` | | `bypassChecks` | `boolean` | `false` | #### Returns `Promise`<`Transaction`<`UnconfirmedWithdrawSilver`\>\> --- ### create ▸ `Static` **create**(`__namedParameters`): `Promise`<[`default`](Backend_GameLogic_GameManager.default.md)\> #### Parameters | Name | Type | | :---------------------------------- | :-------------------------------------------------------------------------------------------------------------- | | `__namedParameters` | `Object` | | `__namedParameters.connection` | `EthConnection` | | `__namedParameters.contractAddress` | `EthAddress` | | `__namedParameters.terminal` | `MutableRefObject`<`undefined` \| [`TerminalHandle`](../interfaces/Frontend_Views_Terminal.TerminalHandle.md)\> | #### Returns `Promise`<[`default`](Backend_GameLogic_GameManager.default.md)\> ================================================ FILE: docs/classes/Backend_GameLogic_GameObjects.GameObjects.md ================================================ # Class: GameObjects [Backend/GameLogic/GameObjects](../modules/Backend_GameLogic_GameObjects.md).GameObjects Representation of the objects which exist in the world. ## Table of contents ### Constructors - [constructor](Backend_GameLogic_GameObjects.GameObjects.md#constructor) ### Properties - [address](Backend_GameLogic_GameObjects.GameObjects.md#address) - [arrivals](Backend_GameLogic_GameObjects.GameObjects.md#arrivals) - [artifactUpdated$](Backend_GameLogic_GameObjects.GameObjects.md#artifactupdated$) - [artifacts](Backend_GameLogic_GameObjects.GameObjects.md#artifacts) - [claimedLocations](Backend_GameLogic_GameObjects.GameObjects.md#claimedlocations) - [contractConstants](Backend_GameLogic_GameObjects.GameObjects.md#contractconstants) - [coordsToLocation](Backend_GameLogic_GameObjects.GameObjects.md#coordstolocation) - [layeredMap](Backend_GameLogic_GameObjects.GameObjects.md#layeredmap) - [myArtifacts](Backend_GameLogic_GameObjects.GameObjects.md#myartifacts) - [myArtifactsUpdated$](Backend_GameLogic_GameObjects.GameObjects.md#myartifactsupdated$) - [myPlanets](Backend_GameLogic_GameObjects.GameObjects.md#myplanets) - [myPlanetsUpdated$](Backend_GameLogic_GameObjects.GameObjects.md#myplanetsupdated$) - [planetArrivalIds](Backend_GameLogic_GameObjects.GameObjects.md#planetarrivalids) - [planetLocationMap](Backend_GameLogic_GameObjects.GameObjects.md#planetlocationmap) - [planetUpdated$](Backend_GameLogic_GameObjects.GameObjects.md#planetupdated$) - [planets](Backend_GameLogic_GameObjects.GameObjects.md#planets) - [revealedLocations](Backend_GameLogic_GameObjects.GameObjects.md#revealedlocations) - [touchedPlanetIds](Backend_GameLogic_GameObjects.GameObjects.md#touchedplanetids) - [transactions](Backend_GameLogic_GameObjects.GameObjects.md#transactions) - [wormholes](Backend_GameLogic_GameObjects.GameObjects.md#wormholes) ### Methods - [addPlanetLocation](Backend_GameLogic_GameObjects.GameObjects.md#addplanetlocation) - [clearOldArrivals](Backend_GameLogic_GameObjects.GameObjects.md#clearoldarrivals) - [clearUnconfirmedTxIntent](Backend_GameLogic_GameObjects.GameObjects.md#clearunconfirmedtxintent) - [defaultPlanetFromLocation](Backend_GameLogic_GameObjects.GameObjects.md#defaultplanetfromlocation) - [emitArrivalNotifications](Backend_GameLogic_GameObjects.GameObjects.md#emitarrivalnotifications) - [forceTick](Backend_GameLogic_GameObjects.GameObjects.md#forcetick) - [getAllOwnedPlanets](Backend_GameLogic_GameObjects.GameObjects.md#getallownedplanets) - [getAllPlanets](Backend_GameLogic_GameObjects.GameObjects.md#getallplanets) - [getAllPlanetsMap](Backend_GameLogic_GameObjects.GameObjects.md#getallplanetsmap) - [getAllVoyages](Backend_GameLogic_GameObjects.GameObjects.md#getallvoyages) - [getArrivalIdsForLocation](Backend_GameLogic_GameObjects.GameObjects.md#getarrivalidsforlocation) - [getArtifactById](Backend_GameLogic_GameObjects.GameObjects.md#getartifactbyid) - [getArtifactController](Backend_GameLogic_GameObjects.GameObjects.md#getartifactcontroller) - [getArtifactMap](Backend_GameLogic_GameObjects.GameObjects.md#getartifactmap) - [getArtifactsOnPlanetsOwnedBy](Backend_GameLogic_GameObjects.GameObjects.md#getartifactsonplanetsownedby) - [getArtifactsOwnedBy](Backend_GameLogic_GameObjects.GameObjects.md#getartifactsownedby) - [getBiome](Backend_GameLogic_GameObjects.GameObjects.md#getbiome) - [getClaimedLocations](Backend_GameLogic_GameObjects.GameObjects.md#getclaimedlocations) - [getEnergyCurveAtPercent](Backend_GameLogic_GameObjects.GameObjects.md#getenergycurveatpercent) - [getLocationOfPlanet](Backend_GameLogic_GameObjects.GameObjects.md#getlocationofplanet) - [getMyArtifactMap](Backend_GameLogic_GameObjects.GameObjects.md#getmyartifactmap) - [getMyPlanetMap](Backend_GameLogic_GameObjects.GameObjects.md#getmyplanetmap) - [getPlanetArtifacts](Backend_GameLogic_GameObjects.GameObjects.md#getplanetartifacts) - [getPlanetDetailLevel](Backend_GameLogic_GameObjects.GameObjects.md#getplanetdetaillevel) - [getPlanetLevel](Backend_GameLogic_GameObjects.GameObjects.md#getplanetlevel) - [getPlanetMap](Backend_GameLogic_GameObjects.GameObjects.md#getplanetmap) - [getPlanetWithCoords](Backend_GameLogic_GameObjects.GameObjects.md#getplanetwithcoords) - [getPlanetWithId](Backend_GameLogic_GameObjects.GameObjects.md#getplanetwithid) - [getPlanetWithLocation](Backend_GameLogic_GameObjects.GameObjects.md#getplanetwithlocation) - [getPlanetsInWorldCircle](Backend_GameLogic_GameObjects.GameObjects.md#getplanetsinworldcircle) - [getPlanetsInWorldRectangle](Backend_GameLogic_GameObjects.GameObjects.md#getplanetsinworldrectangle) - [getPlanetsWithIds](Backend_GameLogic_GameObjects.GameObjects.md#getplanetswithids) - [getRevealedLocations](Backend_GameLogic_GameObjects.GameObjects.md#getrevealedlocations) - [getSilverCurveAtPercent](Backend_GameLogic_GameObjects.GameObjects.md#getsilvercurveatpercent) - [getWormholes](Backend_GameLogic_GameObjects.GameObjects.md#getwormholes) - [isGettingSpaceships](Backend_GameLogic_GameObjects.GameObjects.md#isgettingspaceships) - [isPlanetInContract](Backend_GameLogic_GameObjects.GameObjects.md#isplanetincontract) - [markLocationRevealed](Backend_GameLogic_GameObjects.GameObjects.md#marklocationrevealed) - [onTxIntent](Backend_GameLogic_GameObjects.GameObjects.md#ontxintent) - [planetLevelFromHexPerlin](Backend_GameLogic_GameObjects.GameObjects.md#planetlevelfromhexperlin) - [planetTypeFromHexPerlin](Backend_GameLogic_GameObjects.GameObjects.md#planettypefromhexperlin) - [processArrivalsForPlanet](Backend_GameLogic_GameObjects.GameObjects.md#processarrivalsforplanet) - [removeArrival](Backend_GameLogic_GameObjects.GameObjects.md#removearrival) - [replaceArtifactFromContractData](Backend_GameLogic_GameObjects.GameObjects.md#replaceartifactfromcontractdata) - [replaceArtifactsFromContractData](Backend_GameLogic_GameObjects.GameObjects.md#replaceartifactsfromcontractdata) - [replacePlanetFromContractData](Backend_GameLogic_GameObjects.GameObjects.md#replaceplanetfromcontractdata) - [setArtifact](Backend_GameLogic_GameObjects.GameObjects.md#setartifact) - [setClaimedLocation](Backend_GameLogic_GameObjects.GameObjects.md#setclaimedlocation) - [setPlanet](Backend_GameLogic_GameObjects.GameObjects.md#setplanet) - [spaceTypeFromPerlin](Backend_GameLogic_GameObjects.GameObjects.md#spacetypefromperlin) - [updateArtifact](Backend_GameLogic_GameObjects.GameObjects.md#updateartifact) - [updatePlanet](Backend_GameLogic_GameObjects.GameObjects.md#updateplanet) - [updatePlanetIfStale](Backend_GameLogic_GameObjects.GameObjects.md#updateplanetifstale) - [getSilverNeeded](Backend_GameLogic_GameObjects.GameObjects.md#getsilverneeded) - [planetCanUpgrade](Backend_GameLogic_GameObjects.GameObjects.md#planetcanupgrade) ## Constructors ### constructor • **new GameObjects**(`address`, `touchedPlanets`, `allTouchedPlanetIds`, `revealedLocations`, `claimedLocations`, `artifacts`, `allChunks`, `unprocessedArrivals`, `unprocessedPlanetArrivalIds`, `contractConstants`, `worldRadius`) #### Parameters | Name | Type | | :---------------------------- | :----------------------------------------------------------------------------------------------- | | `address` | `undefined` \| `EthAddress` | | `touchedPlanets` | `Map`<`LocationId`, `Planet`\> | | `allTouchedPlanetIds` | `Set`<`LocationId`\> | | `revealedLocations` | `Map`<`LocationId`, `RevealedLocation`\> | | `claimedLocations` | `Map`<`LocationId`, `ClaimedLocation`\> | | `artifacts` | `Map`<`ArtifactId`, `Artifact`\> | | `allChunks` | `Iterable`<`Chunk`\> | | `unprocessedArrivals` | `Map`<`VoyageId`, `QueuedArrival`\> | | `unprocessedPlanetArrivalIds` | `Map`<`LocationId`, `VoyageId`[]\> | | `contractConstants` | [`ContractConstants`](../interfaces/types_darkforest_api_ContractsAPITypes.ContractConstants.md) | | `worldRadius` | `number` | ## Properties ### address • `Private` `Readonly` **address**: `undefined` \| `EthAddress` This address of the player that is currently logged in. **`todo`** move this, along with all other objects relating to the currently logged-on player into a new field: {@code player: PlayerInfo} --- ### arrivals • `Private` `Readonly` **arrivals**: `Map`<`VoyageId`, `ArrivalWithTimer`\> Map of arrivals to timers that fire when an arrival arrives, in case that handler needs to be cancelled for whatever reason. --- ### artifactUpdated$ • `Readonly` **artifactUpdated$**: `Monomitter`<`ArtifactId`\> Event emitter which publishes whenever an artifact has been updated. --- ### artifacts • `Private` `Readonly` **artifacts**: `Map`<`ArtifactId`, `Artifact`\> Cached index of all known artifact data. **`see`** The same warning applys as the one on [GameObjects.planets](Backend_GameLogic_GameObjects.GameObjects.md#planets) --- ### claimedLocations • `Private` `Readonly` **claimedLocations**: `Map`<`LocationId`, `ClaimedLocation`\> Map from location ids to, if that location id has been claimed on-chain, the world coordinates of that location id, as well as some extra information regarding the circumstances of the revealing of this planet. --- ### contractConstants • `Private` `Readonly` **contractConstants**: [`ContractConstants`](../interfaces/types_darkforest_api_ContractsAPITypes.ContractConstants.md) Some of the game's parameters are downloaded from the blockchain. This allows the client to be flexible, and connect to any compatible set of Dark Forest contracts, download the parameters, and join the game, taking into account the unique configuration of those specific Dark Forest contracts. --- ### coordsToLocation • `Private` `Readonly` **coordsToLocation**: `Map`<`CoordsString`, `WorldLocation`\> Map from a stringified representation of an x-y coordinate to an object that contains some more information about the world at that location. --- ### layeredMap • `Private` `Readonly` **layeredMap**: [`LayeredMap`](Backend_GameLogic_LayeredMap.LayeredMap.md) This is a data structure that allows us to efficiently calculate which planets are visible on the player's screen given the viewport's position and size. --- ### myArtifacts • `Private` `Readonly` **myArtifacts**: `Map`<`ArtifactId`, `Artifact`\> Cached index of artifacts owned by the player. **`see`** The same warning applys as the one on [GameObjects.planets](Backend_GameLogic_GameObjects.GameObjects.md#planets) --- ### myArtifactsUpdated$ • `Readonly` **myArtifactsUpdated$**: `Monomitter`<`Map`<`ArtifactId`, `Artifact`\>\> Whenever one of the player's artifacts are updated, this event emitter publishes. See [GameObjects.myPlanetsUpdated$](Backend_GameLogic_GameObjects.GameObjects.md#myplanetsupdated$) for more info. --- ### myPlanets • `Private` `Readonly` **myPlanets**: `Map`<`LocationId`, `Planet`\> Cached index of planets owned by the player. **`see`** The same warning applys as the one on [GameObjects.planets](Backend_GameLogic_GameObjects.GameObjects.md#planets) --- ### myPlanetsUpdated$ • `Readonly` **myPlanetsUpdated$**: `Monomitter`<`Map`<`LocationId`, `Planet`\>\> Whenever a planet is updated, we publish to this event with a reference to a map from location id to planet. We need to rethink this event emitter because it currently publishes every time that any planet is updated, and if a lot of them are updated at once (which i think is the case once every two minutes) then this event emitter will publish a shitton of events. TODO: rethink this --- ### planetArrivalIds • `Private` `Readonly` **planetArrivalIds**: `Map`<`LocationId`, `VoyageId`[]\> Map from a location id (think of it as the unique id of each planet) to all the ids of the voyages that are arriving on that planet. These include both the player's own voyages, and also any potential invader's voyages. --- ### planetLocationMap • `Private` `Readonly` **planetLocationMap**: `Map`<`LocationId`, `WorldLocation`\> Map from location id (unique id of each planet) to some information about the location at which this planet is located, if this client happens to know the coordinates of this planet. --- ### planetUpdated$ • `Readonly` **planetUpdated$**: `Monomitter`<`LocationId`\> Event emitter which publishes whenever a planet is updated. --- ### planets • `Private` `Readonly` **planets**: `Map`<`LocationId`, `Planet`\> Cached index of all known planet data. Warning! This should NEVER be set to directly! Any time you want to update a planet, you must call the {@link GameObjects#setPlanet()} function. Following this rule enables us to reliably notify other parts of the client when a particular object has been updated. TODO: what is the best way to do this? **`todo`** extract the pattern we're using for the field tuples - {planets, myPlanets, myPlanetsUpdated, planetUpdated$} - {artifacts, myArtifacts, myArtifactsUpdated, artifactUpdated$} into some sort of class. --- ### revealedLocations • `Private` `Readonly` **revealedLocations**: `Map`<`LocationId`, `RevealedLocation`\> Map from location ids to, if that location id has been revealed on-chain, the world coordinates of that location id, as well as some extra information regarding the circumstances of the revealing of this planet. --- ### touchedPlanetIds • `Private` `Readonly` **touchedPlanetIds**: `Set`<`LocationId`\> Set of all planet ids that we know have been interacted-with on-chain. --- ### transactions • `Readonly` **transactions**: `TransactionCollection` Transactions that are currently in flight. --- ### wormholes • `Private` `Readonly` **wormholes**: `Map`<`ArtifactId`, `Wormhole`\> Map from artifact ids to wormholes. ## Methods ### addPlanetLocation ▸ **addPlanetLocation**(`planetLocation`): `void` Called when we load chunk data into memory (on startup), when we're loading all revealed locations (on startup), when miner has mined a new chunk while exploring, and when a planet's location is revealed onchain during the course of play Adds a WorldLocation to the planetLocationMap, making it known to the player locally Sets an unsynced default planet in the PlanetMap this.planets IMPORTANT: This is the only way a LocatablePlanet gets constructed IMPORTANT: Idempotent #### Parameters | Name | Type | | :--------------- | :-------------- | | `planetLocation` | `WorldLocation` | #### Returns `void` --- ### clearOldArrivals ▸ `Private` **clearOldArrivals**(`planet`): `void` #### Parameters | Name | Type | | :------- | :------- | | `planet` | `Planet` | #### Returns `void` --- ### clearUnconfirmedTxIntent ▸ **clearUnconfirmedTxIntent**(`tx`): `void` Whenever a transaction that the user initiated either succeeds or fails, we need to clear the fact that it was in progress from the event's corresponding entities. For example, whenever a transaction that sends a voyage from one planet to another either succeeds or fails, we need to remove the dashed line that connected them. Making sure that we never miss something here is very tedious. **`todo`** Make this less tedious. #### Parameters | Name | Type | | :--- | :------------------------- | | `tx` | `Transaction`<`TxIntent`\> | #### Returns `void` --- ### defaultPlanetFromLocation ▸ `Private` **defaultPlanetFromLocation**(`location`): `LocatablePlanet` returns the data for an unowned, untouched planet at location most planets in the game are untouched and not stored in the contract, so we need to generate their data optimistically in the client #### Parameters | Name | Type | | :--------- | :-------------- | | `location` | `WorldLocation` | #### Returns `LocatablePlanet` --- ### emitArrivalNotifications ▸ `Private` **emitArrivalNotifications**(`__namedParameters`): `void` Emit notifications based on a planet's state change #### Parameters | Name | Type | | :------------------ | :------------------------------------------------------------------------- | | `__namedParameters` | [`PlanetDiff`](../interfaces/Backend_GameLogic_ArrivalUtils.PlanetDiff.md) | #### Returns `void` --- ### forceTick ▸ **forceTick**(`locationId`): `void` #### Parameters | Name | Type | | :----------- | :----------- | | `locationId` | `LocationId` | #### Returns `void` --- ### getAllOwnedPlanets ▸ **getAllOwnedPlanets**(): `Planet`[] Returns all the planets in the game which this client is aware of that have an owner, as a map from their id to the planet **`tutorial`** For plugin developers! **`see`** Warning in {@link GameObjects.getAllPlanets()} #### Returns `Planet`[] --- ### getAllPlanets ▸ **getAllPlanets**(): `Iterable`<`Planet`\> Returns all planets in the game. Warning! Simply iterating over this is not performant, and is meant for scripting. **`tutorial`** For plugin developers! #### Returns `Iterable`<`Planet`\> --- ### getAllPlanetsMap ▸ **getAllPlanetsMap**(): `Map`<`LocationId`, `Planet`\> Returns all planets in the game, as a map from their location id to the planet. **`tutorial`** For plugin developers! **`see`** Warning in {@link GameObjects.getAllPlanets()} #### Returns `Map`<`LocationId`, `Planet`\> --- ### getAllVoyages ▸ **getAllVoyages**(): `QueuedArrival`[] Returns all voyages that are scheduled to arrive at some point in the future. **`tutorial`** For plugin developers! **`see`** Warning in {@link GameObjects.getAllPlanets()} #### Returns `QueuedArrival`[] --- ### getArrivalIdsForLocation ▸ **getArrivalIdsForLocation**(`location`): `undefined` \| `VoyageId`[] Get all of the incoming voyages for a given location. #### Parameters | Name | Type | | :--------- | :-------------------------- | | `location` | `undefined` \| `LocationId` | #### Returns `undefined` \| `VoyageId`[] --- ### getArtifactById ▸ **getArtifactById**(`artifactId?`): `undefined` \| `Artifact` #### Parameters | Name | Type | | :------------ | :----------- | | `artifactId?` | `ArtifactId` | #### Returns `undefined` \| `Artifact` --- ### getArtifactController ▸ **getArtifactController**(`artifactId`): `undefined` \| `EthAddress` Returns the EthAddress of the player who can control the owner: if the artifact is on a planet, this is the owner of the planet if the artifact is on a voyage, this is the initiator of the voyage if the artifact is not on either, then it is the owner of the artifact NFT #### Parameters | Name | Type | | :----------- | :----------- | | `artifactId` | `ArtifactId` | #### Returns `undefined` \| `EthAddress` --- ### getArtifactMap ▸ **getArtifactMap**(): `Map`<`ArtifactId`, `Artifact`\> #### Returns `Map`<`ArtifactId`, `Artifact`\> --- ### getArtifactsOnPlanetsOwnedBy ▸ **getArtifactsOnPlanetsOwnedBy**(`addr`): `Artifact`[] #### Parameters | Name | Type | | :----- | :----------- | | `addr` | `EthAddress` | #### Returns `Artifact`[] --- ### getArtifactsOwnedBy ▸ **getArtifactsOwnedBy**(`addr`): `Artifact`[] #### Parameters | Name | Type | | :----- | :----------- | | `addr` | `EthAddress` | #### Returns `Artifact`[] --- ### getBiome ▸ `Private` **getBiome**(`loc`): `Biome` #### Parameters | Name | Type | | :---- | :-------------- | | `loc` | `WorldLocation` | #### Returns `Biome` --- ### getClaimedLocations ▸ **getClaimedLocations**(): `Map`<`LocationId`, `ClaimedLocation`\> #### Returns `Map`<`LocationId`, `ClaimedLocation`\> --- ### getEnergyCurveAtPercent ▸ **getEnergyCurveAtPercent**(`planet`, `percent`): `number` returns timestamp (seconds) that planet will reach percent% of energycap time may be in the past #### Parameters | Name | Type | | :-------- | :------- | | `planet` | `Planet` | | `percent` | `number` | #### Returns `number` --- ### getLocationOfPlanet ▸ **getLocationOfPlanet**(`planetId`): `undefined` \| `WorldLocation` #### Parameters | Name | Type | | :--------- | :----------- | | `planetId` | `LocationId` | #### Returns `undefined` \| `WorldLocation` --- ### getMyArtifactMap ▸ **getMyArtifactMap**(): `Map`<`ArtifactId`, `Artifact`\> #### Returns `Map`<`ArtifactId`, `Artifact`\> --- ### getMyPlanetMap ▸ **getMyPlanetMap**(): `Map`<`LocationId`, `Planet`\> #### Returns `Map`<`LocationId`, `Planet`\> --- ### getPlanetArtifacts ▸ **getPlanetArtifacts**(`planetId`): `Artifact`[] #### Parameters | Name | Type | | :--------- | :----------- | | `planetId` | `LocationId` | #### Returns `Artifact`[] --- ### getPlanetDetailLevel ▸ **getPlanetDetailLevel**(`planetId`): `undefined` \| `number` #### Parameters | Name | Type | | :--------- | :----------- | | `planetId` | `LocationId` | #### Returns `undefined` \| `number` --- ### getPlanetLevel ▸ **getPlanetLevel**(`planetId`): `undefined` \| `PlanetLevel` #### Parameters | Name | Type | | :--------- | :----------- | | `planetId` | `LocationId` | #### Returns `undefined` \| `PlanetLevel` --- ### getPlanetMap ▸ **getPlanetMap**(): `Map`<`LocationId`, `Planet`\> #### Returns `Map`<`LocationId`, `Planet`\> --- ### getPlanetWithCoords ▸ **getPlanetWithCoords**(`coords`): `undefined` \| `LocatablePlanet` #### Parameters | Name | Type | | :------- | :------------ | | `coords` | `WorldCoords` | #### Returns `undefined` \| `LocatablePlanet` --- ### getPlanetWithId ▸ **getPlanetWithId**(`planetId`, `updateIfStale?`): `undefined` \| `Planet` #### Parameters | Name | Type | Default value | | :-------------- | :----------- | :------------ | | `planetId` | `LocationId` | `undefined` | | `updateIfStale` | `boolean` | `true` | #### Returns `undefined` \| `Planet` --- ### getPlanetWithLocation ▸ **getPlanetWithLocation**(`location`): `undefined` \| `Planet` #### Parameters | Name | Type | | :--------- | :----------------------------- | | `location` | `undefined` \| `WorldLocation` | #### Returns `undefined` \| `Planet` --- ### getPlanetsInWorldCircle ▸ **getPlanetsInWorldCircle**(`coords`, `radius`): `LocatablePlanet`[] Gets all the planets that are within {@code radius} world units from the given coordinate. Fast because it uses [LayeredMap](Backend_GameLogic_LayeredMap.LayeredMap.md). #### Parameters | Name | Type | | :------- | :------------ | | `coords` | `WorldCoords` | | `radius` | `number` | #### Returns `LocatablePlanet`[] --- ### getPlanetsInWorldRectangle ▸ **getPlanetsInWorldRectangle**(`worldX`, `worldY`, `worldWidth`, `worldHeight`, `levels`, `planetLevelToRadii`, `updateIfStale?`): `LocatablePlanet`[] Gets the ids of all the planets that are both within the given bounding box (defined by its bottom left coordinate, width, and height) in the world and of a level that was passed in via the `planetLevels` parameter. Fast because it uses [LayeredMap](Backend_GameLogic_LayeredMap.LayeredMap.md). #### Parameters | Name | Type | Default value | | :------------------- | :------------------------ | :------------ | | `worldX` | `number` | `undefined` | | `worldY` | `number` | `undefined` | | `worldWidth` | `number` | `undefined` | | `worldHeight` | `number` | `undefined` | | `levels` | `number`[] | `undefined` | | `planetLevelToRadii` | `Map`<`number`, `Radii`\> | `undefined` | | `updateIfStale` | `boolean` | `true` | #### Returns `LocatablePlanet`[] --- ### getPlanetsWithIds ▸ **getPlanetsWithIds**(`locationIds`, `updateIfStale?`): `Planet`[] Gets all the planets with the given ids, giltering out the ones that we don't have. #### Parameters | Name | Type | Default value | | :-------------- | :------------- | :------------ | | `locationIds` | `LocationId`[] | `undefined` | | `updateIfStale` | `boolean` | `true` | #### Returns `Planet`[] --- ### getRevealedLocations ▸ **getRevealedLocations**(): `Map`<`LocationId`, `RevealedLocation`\> #### Returns `Map`<`LocationId`, `RevealedLocation`\> --- ### getSilverCurveAtPercent ▸ **getSilverCurveAtPercent**(`planet`, `percent`): `undefined` \| `number` returns timestamp (seconds) that planet will reach percent% of silcap if doesn't produce silver, returns undefined if already over percent% of silcap, returns undefined #### Parameters | Name | Type | | :-------- | :------- | | `planet` | `Planet` | | `percent` | `number` | #### Returns `undefined` \| `number` --- ### getWormholes ▸ **getWormholes**(): `Iterable`<`Wormhole`\> #### Returns `Iterable`<`Wormhole`\> --- ### isGettingSpaceships ▸ **isGettingSpaceships**(): `boolean` Whether or not we're already asking the game to give us spaceships. #### Returns `boolean` --- ### isPlanetInContract ▸ **isPlanetInContract**(`planetId`): `boolean` #### Parameters | Name | Type | | :--------- | :----------- | | `planetId` | `LocationId` | #### Returns `boolean` --- ### markLocationRevealed ▸ **markLocationRevealed**(`revealedLocation`): `void` #### Parameters | Name | Type | | :----------------- | :----------------- | | `revealedLocation` | `RevealedLocation` | #### Returns `void` --- ### onTxIntent ▸ **onTxIntent**(`tx`): `void` We call this function whenever the user requests that we send a transaction to the blockchain with their localstorage wallet. You can think of it as one of the hubs which connects `GameObjects` to the rest of the world. Inside this function, we update the relevant internal game objects to reflect that the user has requested a particular action. Additionally, we publish the appropriate events to the relevant {@link Monomitter} instances that are stored in this class. In the case of something like prospecting for an artifact, this allows us to display a spinner text which says "Prospecting..." In the case of the user sending energy from one planet to another planet, this allows us to display a dashed line between the two planets in their new voyage. Whenever we update an entity, we must do it via that entity's type's corresponding `set` function, in order for us to publish these events. **`todo:`** this entire function could be automated by implementing a new interface called {@code TxFilter}. #### Parameters | Name | Type | | :--- | :------------------------- | | `tx` | `Transaction`<`TxIntent`\> | #### Returns `void` --- ### planetLevelFromHexPerlin ▸ **planetLevelFromHexPerlin**(`hex`, `perlin`): `PlanetLevel` #### Parameters | Name | Type | | :------- | :----------- | | `hex` | `LocationId` | | `perlin` | `number` | #### Returns `PlanetLevel` --- ### planetTypeFromHexPerlin ▸ **planetTypeFromHexPerlin**(`hex`, `perlin`): `PlanetType` #### Parameters | Name | Type | | :------- | :----------- | | `hex` | `LocationId` | | `perlin` | `number` | #### Returns `PlanetType` --- ### processArrivalsForPlanet ▸ `Private` **processArrivalsForPlanet**(`planetId`, `arrivals`): `ArrivalWithTimer`[] #### Parameters | Name | Type | | :--------- | :---------------- | | `planetId` | `LocationId` | | `arrivals` | `QueuedArrival`[] | #### Returns `ArrivalWithTimer`[] --- ### removeArrival ▸ `Private` **removeArrival**(`planetId`, `arrivalId`): `void` #### Parameters | Name | Type | | :---------- | :----------- | | `planetId` | `LocationId` | | `arrivalId` | `VoyageId` | #### Returns `void` --- ### replaceArtifactFromContractData ▸ **replaceArtifactFromContractData**(`artifact`): `void` received some artifact data from the contract. update our stores #### Parameters | Name | Type | | :--------- | :--------- | | `artifact` | `Artifact` | #### Returns `void` --- ### replaceArtifactsFromContractData ▸ **replaceArtifactsFromContractData**(`artifacts`): `void` #### Parameters | Name | Type | | :---------- | :---------------------- | | `artifacts` | `Iterable`<`Artifact`\> | #### Returns `void` --- ### replacePlanetFromContractData ▸ **replacePlanetFromContractData**(`planet`, `updatedArrivals?`, `updatedArtifactsOnPlanet?`, `revealedLocation?`, `claimerEthAddress?`): `void` received some planet data from the contract. update our stores #### Parameters | Name | Type | | :-------------------------- | :----------------- | | `planet` | `Planet` | | `updatedArrivals?` | `QueuedArrival`[] | | `updatedArtifactsOnPlanet?` | `ArtifactId`[] | | `revealedLocation?` | `RevealedLocation` | | `claimerEthAddress?` | `EthAddress` | #### Returns `void` --- ### setArtifact ▸ `Private` **setArtifact**(`artifact`): `void` Set an artifact into our cached store. Should ALWAYS call this when setting an artifact. `this.artifacts` and `this.myArtifacts` should NEVER be accessed directly! This function also handles managing artifact update messages and indexing the map of owned artifacts. #### Parameters | Name | Type | Description | | :--------- | :--------- | :------------------ | | `artifact` | `Artifact` | the artifact to set | #### Returns `void` --- ### setClaimedLocation ▸ **setClaimedLocation**(`claimedLocation`): `void` #### Parameters | Name | Type | | :---------------- | :---------------- | | `claimedLocation` | `ClaimedLocation` | #### Returns `void` --- ### setPlanet ▸ `Private` **setPlanet**(`planet`): `void` Set a planet into our cached store. Should ALWAYS call this when setting a planet. `this.planets` and `this.myPlanets` should NEVER be accessed directly! This function also handles managing planet update messages and indexing the map of owned planets. #### Parameters | Name | Type | Description | | :------- | :------- | :---------------- | | `planet` | `Planet` | the planet to set | #### Returns `void` --- ### spaceTypeFromPerlin ▸ **spaceTypeFromPerlin**(`perlin`): `SpaceType` #### Parameters | Name | Type | | :------- | :------- | | `perlin` | `number` | #### Returns `SpaceType` --- ### updateArtifact ▸ **updateArtifact**(`id`, `updateFn`): `void` Given a planet id, update the state of the given planet by calling the given update function. If the planet was updated, then also publish the appropriate event. #### Parameters | Name | Type | | :--------- | :-------------------------- | | `id` | `undefined` \| `ArtifactId` | | `updateFn` | (`p`: `Artifact`) => `void` | #### Returns `void` --- ### updatePlanet ▸ **updatePlanet**(`id`, `updateFn`): `void` Given a planet id, update the state of the given planet by calling the given update function. If the planet was updated, then also publish the appropriate event. #### Parameters | Name | Type | | :--------- | :------------------------ | | `id` | `LocationId` | | `updateFn` | (`p`: `Planet`) => `void` | #### Returns `void` --- ### updatePlanetIfStale ▸ `Private` **updatePlanetIfStale**(`planet`): `void` #### Parameters | Name | Type | | :------- | :------- | | `planet` | `Planet` | #### Returns `void` --- ### getSilverNeeded ▸ `Static` **getSilverNeeded**(`planet`): `number` #### Parameters | Name | Type | | :------- | :------- | | `planet` | `Planet` | #### Returns `number` --- ### planetCanUpgrade ▸ `Static` **planetCanUpgrade**(`planet`): `boolean` #### Parameters | Name | Type | | :------- | :------- | | `planet` | `Planet` | #### Returns `boolean` ================================================ FILE: docs/classes/Backend_GameLogic_GameUIManager.default.md ================================================ # Class: default [Backend/GameLogic/GameUIManager](../modules/Backend_GameLogic_GameUIManager.md).default ## Hierarchy - `EventEmitter` ↳ **`default`** ## Table of contents ### Constructors - [constructor](Backend_GameLogic_GameUIManager.default.md#constructor) ### Properties - [abandoning](Backend_GameLogic_GameUIManager.default.md#abandoning) - [artifactSending](Backend_GameLogic_GameUIManager.default.md#artifactsending) - [extraMinerLocations](Backend_GameLogic_GameUIManager.default.md#extraminerlocations) - [forcesSending](Backend_GameLogic_GameUIManager.default.md#forcessending) - [gameManager](Backend_GameLogic_GameUIManager.default.md#gamemanager) - [hoverArtifact$](Backend_GameLogic_GameUIManager.default.md#hoverartifact$) - [hoverArtifactId$](Backend_GameLogic_GameUIManager.default.md#hoverartifactid$) - [hoverPlanet$](Backend_GameLogic_GameUIManager.default.md#hoverplanet$) - [hoverPlanetId$](Backend_GameLogic_GameUIManager.default.md#hoverplanetid$) - [isAbandoning$](Backend_GameLogic_GameUIManager.default.md#isabandoning$) - [isChoosingTargetPlanet](Backend_GameLogic_GameUIManager.default.md#ischoosingtargetplanet) - [isSending](Backend_GameLogic_GameUIManager.default.md#issending) - [isSending$](Backend_GameLogic_GameUIManager.default.md#issending$) - [minerLocation](Backend_GameLogic_GameUIManager.default.md#minerlocation) - [modalManager](Backend_GameLogic_GameUIManager.default.md#modalmanager) - [mouseDownOverCoords](Backend_GameLogic_GameUIManager.default.md#mousedownovercoords) - [mouseDownOverPlanet](Backend_GameLogic_GameUIManager.default.md#mousedownoverplanet) - [mouseHoveringOverCoords](Backend_GameLogic_GameUIManager.default.md#mousehoveringovercoords) - [mouseHoveringOverPlanet](Backend_GameLogic_GameUIManager.default.md#mousehoveringoverplanet) - [myArtifacts$](Backend_GameLogic_GameUIManager.default.md#myartifacts$) - [onChooseTargetPlanet](Backend_GameLogic_GameUIManager.default.md#onchoosetargetplanet) - [overlayContainer](Backend_GameLogic_GameUIManager.default.md#overlaycontainer) - [planetHoveringInRenderer](Backend_GameLogic_GameUIManager.default.md#planethoveringinrenderer) - [plugins](Backend_GameLogic_GameUIManager.default.md#plugins) - [previousSelectedPlanetId](Backend_GameLogic_GameUIManager.default.md#previousselectedplanetid) - [radiusMap](Backend_GameLogic_GameUIManager.default.md#radiusmap) - [selectedCoords](Backend_GameLogic_GameUIManager.default.md#selectedcoords) - [selectedPlanetId](Backend_GameLogic_GameUIManager.default.md#selectedplanetid) - [selectedPlanetId$](Backend_GameLogic_GameUIManager.default.md#selectedplanetid$) - [sendingCoords](Backend_GameLogic_GameUIManager.default.md#sendingcoords) - [sendingPlanet](Backend_GameLogic_GameUIManager.default.md#sendingplanet) - [silverSending](Backend_GameLogic_GameUIManager.default.md#silversending) - [terminal](Backend_GameLogic_GameUIManager.default.md#terminal) - [viewportEntities](Backend_GameLogic_GameUIManager.default.md#viewportentities) ### Accessors - [captureZonesEnabled](Backend_GameLogic_GameUIManager.default.md#capturezonesenabled) - [contractConstants](Backend_GameLogic_GameUIManager.default.md#contractconstants) ### Methods - [activateArtifact](Backend_GameLogic_GameUIManager.default.md#activateartifact) - [addAccount](Backend_GameLogic_GameUIManager.default.md#addaccount) - [addNewChunk](Backend_GameLogic_GameUIManager.default.md#addnewchunk) - [bulkAddNewChunks](Backend_GameLogic_GameUIManager.default.md#bulkaddnewchunks) - [buyHat](Backend_GameLogic_GameUIManager.default.md#buyhat) - [centerCoords](Backend_GameLogic_GameUIManager.default.md#centercoords) - [centerLocationId](Backend_GameLogic_GameUIManager.default.md#centerlocationid) - [centerPlanet](Backend_GameLogic_GameUIManager.default.md#centerplanet) - [deactivateArtifact](Backend_GameLogic_GameUIManager.default.md#deactivateartifact) - [depositArtifact](Backend_GameLogic_GameUIManager.default.md#depositartifact) - [destroy](Backend_GameLogic_GameUIManager.default.md#destroy) - [disableCustomRenderer](Backend_GameLogic_GameUIManager.default.md#disablecustomrenderer) - [disconnectTwitter](Backend_GameLogic_GameUIManager.default.md#disconnecttwitter) - [discoverBiome](Backend_GameLogic_GameUIManager.default.md#discoverbiome) - [drawAllRunningPlugins](Backend_GameLogic_GameUIManager.default.md#drawallrunningplugins) - [findArtifact](Backend_GameLogic_GameUIManager.default.md#findartifact) - [generateVerificationTweet](Backend_GameLogic_GameUIManager.default.md#generateverificationtweet) - [get2dRenderer](Backend_GameLogic_GameUIManager.default.md#get2drenderer) - [getAbandonRangeChangePercent](Backend_GameLogic_GameUIManager.default.md#getabandonrangechangepercent) - [getAbandonSpeedChangePercent](Backend_GameLogic_GameUIManager.default.md#getabandonspeedchangepercent) - [getAccount](Backend_GameLogic_GameUIManager.default.md#getaccount) - [getAllMinerLocations](Backend_GameLogic_GameUIManager.default.md#getallminerlocations) - [getAllOwnedPlanets](Backend_GameLogic_GameUIManager.default.md#getallownedplanets) - [getAllPlayers](Backend_GameLogic_GameUIManager.default.md#getallplayers) - [getAllVoyages](Backend_GameLogic_GameUIManager.default.md#getallvoyages) - [getArtifactMap](Backend_GameLogic_GameUIManager.default.md#getartifactmap) - [getArtifactPlanet](Backend_GameLogic_GameUIManager.default.md#getartifactplanet) - [getArtifactPointValues](Backend_GameLogic_GameUIManager.default.md#getartifactpointvalues) - [getArtifactSending](Backend_GameLogic_GameUIManager.default.md#getartifactsending) - [getArtifactUpdated$](Backend_GameLogic_GameUIManager.default.md#getartifactupdated$) - [getArtifactWithId](Backend_GameLogic_GameUIManager.default.md#getartifactwithid) - [getArtifactsWithIds](Backend_GameLogic_GameUIManager.default.md#getartifactswithids) - [getBiomeKey](Backend_GameLogic_GameUIManager.default.md#getbiomekey) - [getBiomePerlin](Backend_GameLogic_GameUIManager.default.md#getbiomeperlin) - [getBooleanSetting](Backend_GameLogic_GameUIManager.default.md#getbooleansetting) - [getCaptureZoneGenerator](Backend_GameLogic_GameUIManager.default.md#getcapturezonegenerator) - [getCaptureZonePointValues](Backend_GameLogic_GameUIManager.default.md#getcapturezonepointvalues) - [getCaptureZones](Backend_GameLogic_GameUIManager.default.md#getcapturezones) - [getChunk](Backend_GameLogic_GameUIManager.default.md#getchunk) - [getContractAddress](Backend_GameLogic_GameUIManager.default.md#getcontractaddress) - [getDefaultSpaceJunkForPlanetLevel](Backend_GameLogic_GameUIManager.default.md#getdefaultspacejunkforplanetlevel) - [getDiagnostics](Backend_GameLogic_GameUIManager.default.md#getdiagnostics) - [getDiscoverBiomeName](Backend_GameLogic_GameUIManager.default.md#getdiscoverbiomename) - [getDistCoords](Backend_GameLogic_GameUIManager.default.md#getdistcoords) - [getEndTimeSeconds](Backend_GameLogic_GameUIManager.default.md#getendtimeseconds) - [getEnergyArrivingForMove](Backend_GameLogic_GameUIManager.default.md#getenergyarrivingformove) - [getEnergyCurveAtPercent](Backend_GameLogic_GameUIManager.default.md#getenergycurveatpercent) - [getEnergyOfPlayer](Backend_GameLogic_GameUIManager.default.md#getenergyofplayer) - [getEthConnection](Backend_GameLogic_GameUIManager.default.md#getethconnection) - [getExploredChunks](Backend_GameLogic_GameUIManager.default.md#getexploredchunks) - [getForcesSending](Backend_GameLogic_GameUIManager.default.md#getforcessending) - [getGameManager](Backend_GameLogic_GameUIManager.default.md#getgamemanager) - [getGameObjects](Backend_GameLogic_GameUIManager.default.md#getgameobjects) - [getGlManager](Backend_GameLogic_GameUIManager.default.md#getglmanager) - [getHashConfig](Backend_GameLogic_GameUIManager.default.md#gethashconfig) - [getHashesPerSec](Backend_GameLogic_GameUIManager.default.md#gethashespersec) - [getHomeCoords](Backend_GameLogic_GameUIManager.default.md#gethomecoords) - [getHomeHash](Backend_GameLogic_GameUIManager.default.md#gethomehash) - [getHomePlanet](Backend_GameLogic_GameUIManager.default.md#gethomeplanet) - [getHoveringOverCoords](Backend_GameLogic_GameUIManager.default.md#gethoveringovercoords) - [getHoveringOverPlanet](Backend_GameLogic_GameUIManager.default.md#gethoveringoverplanet) - [getIsChoosingTargetPlanet](Backend_GameLogic_GameUIManager.default.md#getischoosingtargetplanet) - [getIsHighPerfMode](Backend_GameLogic_GameUIManager.default.md#getishighperfmode) - [getLocationOfPlanet](Backend_GameLogic_GameUIManager.default.md#getlocationofplanet) - [getLocationsAndChunks](Backend_GameLogic_GameUIManager.default.md#getlocationsandchunks) - [getMinerLocation](Backend_GameLogic_GameUIManager.default.md#getminerlocation) - [getMiningPattern](Backend_GameLogic_GameUIManager.default.md#getminingpattern) - [getModalManager](Backend_GameLogic_GameUIManager.default.md#getmodalmanager) - [getMouseDownCoords](Backend_GameLogic_GameUIManager.default.md#getmousedowncoords) - [getMouseDownPlanet](Backend_GameLogic_GameUIManager.default.md#getmousedownplanet) - [getMyArtifactMap](Backend_GameLogic_GameUIManager.default.md#getmyartifactmap) - [getMyArtifacts](Backend_GameLogic_GameUIManager.default.md#getmyartifacts) - [getMyArtifactsNotOnPlanet](Backend_GameLogic_GameUIManager.default.md#getmyartifactsnotonplanet) - [getMyBalance](Backend_GameLogic_GameUIManager.default.md#getmybalance) - [getMyBalance$](Backend_GameLogic_GameUIManager.default.md#getmybalance$) - [getMyBalanceBn](Backend_GameLogic_GameUIManager.default.md#getmybalancebn) - [getMyPlanetMap](Backend_GameLogic_GameUIManager.default.md#getmyplanetmap) - [getMyScore](Backend_GameLogic_GameUIManager.default.md#getmyscore) - [getNextBroadcastAvailableTimestamp](Backend_GameLogic_GameUIManager.default.md#getnextbroadcastavailabletimestamp) - [getOverlayContainer](Backend_GameLogic_GameUIManager.default.md#getoverlaycontainer) - [getPaused](Backend_GameLogic_GameUIManager.default.md#getpaused) - [getPaused$](Backend_GameLogic_GameUIManager.default.md#getpaused$) - [getPerlinConfig](Backend_GameLogic_GameUIManager.default.md#getperlinconfig) - [getPerlinThresholds](Backend_GameLogic_GameUIManager.default.md#getperlinthresholds) - [getPlanetHoveringInRenderer](Backend_GameLogic_GameUIManager.default.md#getplanethoveringinrenderer) - [getPlanetLevel](Backend_GameLogic_GameUIManager.default.md#getplanetlevel) - [getPlanetMap](Backend_GameLogic_GameUIManager.default.md#getplanetmap) - [getPlanetWithCoords](Backend_GameLogic_GameUIManager.default.md#getplanetwithcoords) - [getPlanetWithId](Backend_GameLogic_GameUIManager.default.md#getplanetwithid) - [getPlanetsInViewport](Backend_GameLogic_GameUIManager.default.md#getplanetsinviewport) - [getPlayer](Backend_GameLogic_GameUIManager.default.md#getplayer) - [getPlayerScore](Backend_GameLogic_GameUIManager.default.md#getplayerscore) - [getPluginManager](Backend_GameLogic_GameUIManager.default.md#getpluginmanager) - [getPreviousSelectedPlanet](Backend_GameLogic_GameUIManager.default.md#getpreviousselectedplanet) - [getPrivateKey](Backend_GameLogic_GameUIManager.default.md#getprivatekey) - [getRadiusOfPlanetLevel](Backend_GameLogic_GameUIManager.default.md#getradiusofplanetlevel) - [getRangeBuff](Backend_GameLogic_GameUIManager.default.md#getrangebuff) - [getRenderer](Backend_GameLogic_GameUIManager.default.md#getrenderer) - [getSelectedCoords](Backend_GameLogic_GameUIManager.default.md#getselectedcoords) - [getSelectedPlanet](Backend_GameLogic_GameUIManager.default.md#getselectedplanet) - [getSilverCurveAtPercent](Backend_GameLogic_GameUIManager.default.md#getsilvercurveatpercent) - [getSilverOfPlayer](Backend_GameLogic_GameUIManager.default.md#getsilverofplayer) - [getSilverScoreValue](Backend_GameLogic_GameUIManager.default.md#getsilverscorevalue) - [getSilverSending](Backend_GameLogic_GameUIManager.default.md#getsilversending) - [getSpaceJunkEnabled](Backend_GameLogic_GameUIManager.default.md#getspacejunkenabled) - [getSpaceTypePerlin](Backend_GameLogic_GameUIManager.default.md#getspacetypeperlin) - [getSpeedBuff](Backend_GameLogic_GameUIManager.default.md#getspeedbuff) - [getStringSetting](Backend_GameLogic_GameUIManager.default.md#getstringsetting) - [getTerminal](Backend_GameLogic_GameUIManager.default.md#getterminal) - [getTwitter](Backend_GameLogic_GameUIManager.default.md#gettwitter) - [getUIEmitter](Backend_GameLogic_GameUIManager.default.md#getuiemitter) - [getUnconfirmedMoves](Backend_GameLogic_GameUIManager.default.md#getunconfirmedmoves) - [getUnconfirmedUpgrades](Backend_GameLogic_GameUIManager.default.md#getunconfirmedupgrades) - [getUnconfirmedWormholeActivations](Backend_GameLogic_GameUIManager.default.md#getunconfirmedwormholeactivations) - [getUniverseTotalEnergy](Backend_GameLogic_GameUIManager.default.md#getuniversetotalenergy) - [getUpgrade](Backend_GameLogic_GameUIManager.default.md#getupgrade) - [getViewport](Backend_GameLogic_GameUIManager.default.md#getviewport) - [getWorldRadius](Backend_GameLogic_GameUIManager.default.md#getworldradius) - [getWorldSilver](Backend_GameLogic_GameUIManager.default.md#getworldsilver) - [getWormholes](Backend_GameLogic_GameUIManager.default.md#getwormholes) - [hasMinedChunk](Backend_GameLogic_GameUIManager.default.md#hasminedchunk) - [isAbandoning](Backend_GameLogic_GameUIManager.default.md#isabandoning) - [isAdmin](Backend_GameLogic_GameUIManager.default.md#isadmin) - [isCurrentlyRevealing](Backend_GameLogic_GameUIManager.default.md#iscurrentlyrevealing) - [isMining](Backend_GameLogic_GameUIManager.default.md#ismining) - [isOverOwnPlanet](Backend_GameLogic_GameUIManager.default.md#isoverownplanet) - [isOwnedByMe](Backend_GameLogic_GameUIManager.default.md#isownedbyme) - [isRoundOver](Backend_GameLogic_GameUIManager.default.md#isroundover) - [isSendingForces](Backend_GameLogic_GameUIManager.default.md#issendingforces) - [isSendingShip](Backend_GameLogic_GameUIManager.default.md#issendingship) - [joinGame](Backend_GameLogic_GameUIManager.default.md#joingame) - [onDiscoveredChunk](Backend_GameLogic_GameUIManager.default.md#ondiscoveredchunk) - [onEmitInitializedPlayer](Backend_GameLogic_GameUIManager.default.md#onemitinitializedplayer) - [onEmitInitializedPlayerError](Backend_GameLogic_GameUIManager.default.md#onemitinitializedplayererror) - [onMouseClick](Backend_GameLogic_GameUIManager.default.md#onmouseclick) - [onMouseDown](Backend_GameLogic_GameUIManager.default.md#onmousedown) - [onMouseMove](Backend_GameLogic_GameUIManager.default.md#onmousemove) - [onMouseOut](Backend_GameLogic_GameUIManager.default.md#onmouseout) - [onMouseUp](Backend_GameLogic_GameUIManager.default.md#onmouseup) - [onSendCancel](Backend_GameLogic_GameUIManager.default.md#onsendcancel) - [onSendComplete](Backend_GameLogic_GameUIManager.default.md#onsendcomplete) - [onSendInit](Backend_GameLogic_GameUIManager.default.md#onsendinit) - [potentialCaptureScore](Backend_GameLogic_GameUIManager.default.md#potentialcapturescore) - [prospectPlanet](Backend_GameLogic_GameUIManager.default.md#prospectplanet) - [removeExtraMinerLocation](Backend_GameLogic_GameUIManager.default.md#removeextraminerlocation) - [revealLocation](Backend_GameLogic_GameUIManager.default.md#reveallocation) - [setAbandoning](Backend_GameLogic_GameUIManager.default.md#setabandoning) - [setArtifactSending](Backend_GameLogic_GameUIManager.default.md#setartifactsending) - [setCustomRenderer](Backend_GameLogic_GameUIManager.default.md#setcustomrenderer) - [setExtraMinerLocation](Backend_GameLogic_GameUIManager.default.md#setextraminerlocation) - [setForcesSending](Backend_GameLogic_GameUIManager.default.md#setforcessending) - [setHoveringOverArtifact](Backend_GameLogic_GameUIManager.default.md#sethoveringoverartifact) - [setHoveringOverPlanet](Backend_GameLogic_GameUIManager.default.md#sethoveringoverplanet) - [setMiningPattern](Backend_GameLogic_GameUIManager.default.md#setminingpattern) - [setModalManager](Backend_GameLogic_GameUIManager.default.md#setmodalmanager) - [setOverlayContainer](Backend_GameLogic_GameUIManager.default.md#setoverlaycontainer) - [setSelectedId](Backend_GameLogic_GameUIManager.default.md#setselectedid) - [setSelectedPlanet](Backend_GameLogic_GameUIManager.default.md#setselectedplanet) - [setSending](Backend_GameLogic_GameUIManager.default.md#setsending) - [setSilverSending](Backend_GameLogic_GameUIManager.default.md#setsilversending) - [spaceTypeFromPerlin](Backend_GameLogic_GameUIManager.default.md#spacetypefromperlin) - [startExplore](Backend_GameLogic_GameUIManager.default.md#startexplore) - [startWormholeFrom](Backend_GameLogic_GameUIManager.default.md#startwormholefrom) - [stopExplore](Backend_GameLogic_GameUIManager.default.md#stopexplore) - [timeUntilNextBroadcastAvailable](Backend_GameLogic_GameUIManager.default.md#timeuntilnextbroadcastavailable) - [toggleExplore](Backend_GameLogic_GameUIManager.default.md#toggleexplore) - [toggleTargettingExplorer](Backend_GameLogic_GameUIManager.default.md#toggletargettingexplorer) - [updateDiagnostics](Backend_GameLogic_GameUIManager.default.md#updatediagnostics) - [updateMouseHoveringOverCoords](Backend_GameLogic_GameUIManager.default.md#updatemousehoveringovercoords) - [updatePlanets](Backend_GameLogic_GameUIManager.default.md#updateplanets) - [upgrade](Backend_GameLogic_GameUIManager.default.md#upgrade) - [verifyTwitter](Backend_GameLogic_GameUIManager.default.md#verifytwitter) - [withdrawArtifact](Backend_GameLogic_GameUIManager.default.md#withdrawartifact) - [withdrawSilver](Backend_GameLogic_GameUIManager.default.md#withdrawsilver) - [create](Backend_GameLogic_GameUIManager.default.md#create) ## Constructors ### constructor • `Private` **new default**(`gameManager`, `terminalHandle`) #### Parameters | Name | Type | | :--------------- | :-------------------------------------------------------------------------------------------------------------- | | `gameManager` | [`default`](Backend_GameLogic_GameManager.default.md) | | `terminalHandle` | `MutableRefObject`<`undefined` \| [`TerminalHandle`](../interfaces/Frontend_Views_Terminal.TerminalHandle.md)\> | #### Overrides EventEmitter.constructor ## Properties ### abandoning • `Private` **abandoning**: `boolean` = `false` --- ### artifactSending • `Private` **artifactSending**: `Object` = `{}` #### Index signature ▪ [key: `string`]: `Artifact` \| `undefined` --- ### extraMinerLocations • `Private` **extraMinerLocations**: `WorldCoords`[] = `[]` --- ### forcesSending • `Private` **forcesSending**: `Object` = `{}` #### Index signature ▪ [key: `string`]: `number` --- ### gameManager • `Private` `Readonly` **gameManager**: [`default`](Backend_GameLogic_GameManager.default.md) --- ### hoverArtifact$ • `Readonly` **hoverArtifact$**: `Monomitter`<`undefined` \| `Artifact`\> --- ### hoverArtifactId$ • `Readonly` **hoverArtifactId$**: `Monomitter`<`undefined` \| `ArtifactId`\> --- ### hoverPlanet$ • `Readonly` **hoverPlanet$**: `Monomitter`<`undefined` \| `Planet`\> --- ### hoverPlanetId$ • `Readonly` **hoverPlanetId$**: `Monomitter`<`undefined` \| `LocationId`\> --- ### isAbandoning$ • `Readonly` **isAbandoning$**: `Monomitter`<`boolean`\> --- ### isChoosingTargetPlanet • `Private` **isChoosingTargetPlanet**: `boolean` = `false` The Wormhole artifact requires you to choose a target planet. This value indicates whether or not the player is currently selecting a target planet. --- ### isSending • `Private` **isSending**: `boolean` = `false` --- ### isSending$ • `Readonly` **isSending$**: `Monomitter`<`boolean`\> --- ### minerLocation • `Private` **minerLocation**: `undefined` \| `WorldCoords` --- ### modalManager • `Private` **modalManager**: [`default`](Frontend_Game_ModalManager.default.md) --- ### mouseDownOverCoords • `Private` **mouseDownOverCoords**: `undefined` \| `WorldCoords` --- ### mouseDownOverPlanet • `Private` **mouseDownOverPlanet**: `undefined` \| `LocatablePlanet` --- ### mouseHoveringOverCoords • `Private` **mouseHoveringOverCoords**: `undefined` \| `WorldCoords` --- ### mouseHoveringOverPlanet • `Private` **mouseHoveringOverPlanet**: `undefined` \| `LocatablePlanet` --- ### myArtifacts$ • `Readonly` **myArtifacts$**: `Monomitter`<`Map`<`ArtifactId`, `Artifact`\>\> --- ### onChooseTargetPlanet • `Private` `Optional` **onChooseTargetPlanet**: (`planet`: `undefined` \| `LocatablePlanet`) => `void` #### Type declaration ▸ (`planet`): `void` ##### Parameters | Name | Type | | :------- | :------------------------------- | | `planet` | `undefined` \| `LocatablePlanet` | ##### Returns `void` --- ### overlayContainer • `Private` `Optional` **overlayContainer**: `HTMLDivElement` In order to render React on top of the game, we need to insert React nodes into an overlay container. We keep a reference to this container, so that our React components can optionally choose to render themselves into this overlay container using React Portals. --- ### planetHoveringInRenderer • `Private` **planetHoveringInRenderer**: `boolean` = `false` --- ### plugins • `Private` **plugins**: [`PluginManager`](Backend_GameLogic_PluginManager.PluginManager.md) --- ### previousSelectedPlanetId • `Private` **previousSelectedPlanetId**: `undefined` \| `LocationId` --- ### radiusMap • `Private` `Readonly` **radiusMap**: `Object` #### Index signature ▪ [PlanetLevel: `number`]: `number` --- ### selectedCoords • `Private` **selectedCoords**: `undefined` \| `WorldCoords` --- ### selectedPlanetId • `Private` **selectedPlanetId**: `undefined` \| `LocationId` --- ### selectedPlanetId$ • `Readonly` **selectedPlanetId$**: `Monomitter`<`undefined` \| `LocationId`\> --- ### sendingCoords • `Private` **sendingCoords**: `undefined` \| `WorldCoords` --- ### sendingPlanet • `Private` **sendingPlanet**: `undefined` \| `LocatablePlanet` --- ### silverSending • `Private` **silverSending**: `Object` = `{}` #### Index signature ▪ [key: `string`]: `number` --- ### terminal • `Private` **terminal**: `MutableRefObject`<`undefined` \| [`TerminalHandle`](../interfaces/Frontend_Views_Terminal.TerminalHandle.md)\> --- ### viewportEntities • `Private` **viewportEntities**: [`ViewportEntities`](Backend_GameLogic_ViewportEntities.ViewportEntities.md) ## Accessors ### captureZonesEnabled • `get` **captureZonesEnabled**(): `boolean` #### Returns `boolean` --- ### contractConstants • `get` **contractConstants**(): [`ContractConstants`](../interfaces/types_darkforest_api_ContractsAPITypes.ContractConstants.md) #### Returns [`ContractConstants`](../interfaces/types_darkforest_api_ContractsAPITypes.ContractConstants.md) ## Methods ### activateArtifact ▸ **activateArtifact**(`locationId`, `id`, `wormholeTo?`): `void` #### Parameters | Name | Type | | :------------ | :----------- | | `locationId` | `LocationId` | | `id` | `ArtifactId` | | `wormholeTo?` | `LocationId` | #### Returns `void` --- ### addAccount ▸ **addAccount**(`coords`): `Promise`<`boolean`\> #### Parameters | Name | Type | | :------- | :------------ | | `coords` | `WorldCoords` | #### Returns `Promise`<`boolean`\> --- ### addNewChunk ▸ **addNewChunk**(`chunk`): `void` #### Parameters | Name | Type | | :------ | :------ | | `chunk` | `Chunk` | #### Returns `void` --- ### bulkAddNewChunks ▸ **bulkAddNewChunks**(`chunks`): `Promise`<`void`\> #### Parameters | Name | Type | | :------- | :-------- | | `chunks` | `Chunk`[] | #### Returns `Promise`<`void`\> --- ### buyHat ▸ **buyHat**(`planet`): `void` #### Parameters | Name | Type | | :------- | :------- | | `planet` | `Planet` | #### Returns `void` --- ### centerCoords ▸ **centerCoords**(`coords`): `void` #### Parameters | Name | Type | | :------- | :------------ | | `coords` | `WorldCoords` | #### Returns `void` --- ### centerLocationId ▸ **centerLocationId**(`planetId`): `void` #### Parameters | Name | Type | | :--------- | :----------- | | `planetId` | `LocationId` | #### Returns `void` --- ### centerPlanet ▸ **centerPlanet**(`planet`): `void` #### Parameters | Name | Type | | :------- | :------------------------------- | | `planet` | `undefined` \| `LocatablePlanet` | #### Returns `void` --- ### deactivateArtifact ▸ **deactivateArtifact**(`locationId`, `artifactId`): `void` #### Parameters | Name | Type | | :----------- | :----------- | | `locationId` | `LocationId` | | `artifactId` | `ArtifactId` | #### Returns `void` --- ### depositArtifact ▸ **depositArtifact**(`locationId`, `artifactId`): `void` #### Parameters | Name | Type | | :----------- | :----------- | | `locationId` | `LocationId` | | `artifactId` | `ArtifactId` | #### Returns `void` --- ### destroy ▸ **destroy**(): `void` #### Returns `void` --- ### disableCustomRenderer ▸ **disableCustomRenderer**(`customRenderer`): `void` This function will remove the passed in renderer from the renderering stack. If the renderer is on top of the renderering stack the next renderer will be the one bellow it. #### Parameters | Name | Type | Description | | :--------------- | :------------- | :------------------------------------------------------- | | `customRenderer` | `BaseRenderer` | a Renderer that follows one of the 23 renderer tempaltes | #### Returns `void` --- ### disconnectTwitter ▸ **disconnectTwitter**(`twitter`): `Promise`<`void`\> #### Parameters | Name | Type | | :-------- | :------- | | `twitter` | `string` | #### Returns `Promise`<`void`\> --- ### discoverBiome ▸ **discoverBiome**(`planet`): `void` #### Parameters | Name | Type | | :------- | :---------------- | | `planet` | `LocatablePlanet` | #### Returns `void` --- ### drawAllRunningPlugins ▸ **drawAllRunningPlugins**(`ctx`): `void` #### Parameters | Name | Type | | :---- | :------------------------- | | `ctx` | `CanvasRenderingContext2D` | #### Returns `void` --- ### findArtifact ▸ **findArtifact**(`planetId`): `void` #### Parameters | Name | Type | | :--------- | :----------- | | `planetId` | `LocationId` | #### Returns `void` --- ### generateVerificationTweet ▸ **generateVerificationTweet**(`twitter`): `Promise`<`string`\> #### Parameters | Name | Type | | :-------- | :------- | | `twitter` | `string` | #### Returns `Promise`<`string`\> --- ### get2dRenderer ▸ **get2dRenderer**(): `null` \| `CanvasRenderingContext2D` #### Returns `null` \| `CanvasRenderingContext2D` the CanvasRenderingContext2D for the game canvas. --- ### getAbandonRangeChangePercent ▸ **getAbandonRangeChangePercent**(): `number` #### Returns `number` --- ### getAbandonSpeedChangePercent ▸ **getAbandonSpeedChangePercent**(): `number` #### Returns `number` --- ### getAccount ▸ **getAccount**(): `undefined` \| `EthAddress` #### Returns `undefined` \| `EthAddress` --- ### getAllMinerLocations ▸ **getAllMinerLocations**(): `WorldCoords`[] #### Returns `WorldCoords`[] --- ### getAllOwnedPlanets ▸ **getAllOwnedPlanets**(): `Planet`[] #### Returns `Planet`[] --- ### getAllPlayers ▸ **getAllPlayers**(): `Player`[] #### Returns `Player`[] --- ### getAllVoyages ▸ **getAllVoyages**(): `QueuedArrival`[] #### Returns `QueuedArrival`[] --- ### getArtifactMap ▸ **getArtifactMap**(): `Map`<`ArtifactId`, `Artifact`\> #### Returns `Map`<`ArtifactId`, `Artifact`\> --- ### getArtifactPlanet ▸ **getArtifactPlanet**(`artifact`): `undefined` \| `Planet` #### Parameters | Name | Type | | :--------- | :--------- | | `artifact` | `Artifact` | #### Returns `undefined` \| `Planet` --- ### getArtifactPointValues ▸ **getArtifactPointValues**(): `ArtifactPointValues` #### Returns `ArtifactPointValues` --- ### getArtifactSending ▸ **getArtifactSending**(`planetId?`): `undefined` \| `Artifact` #### Parameters | Name | Type | | :---------- | :----------- | | `planetId?` | `LocationId` | #### Returns `undefined` \| `Artifact` --- ### getArtifactUpdated$ ▸ **getArtifactUpdated$**(): `Monomitter`<`ArtifactId`\> #### Returns `Monomitter`<`ArtifactId`\> --- ### getArtifactWithId ▸ **getArtifactWithId**(`artifactId`): `undefined` \| `Artifact` #### Parameters | Name | Type | | :----------- | :-------------------------- | | `artifactId` | `undefined` \| `ArtifactId` | #### Returns `undefined` \| `Artifact` --- ### getArtifactsWithIds ▸ **getArtifactsWithIds**(`artifactIds?`): (`undefined` \| `Artifact`)[] #### Parameters | Name | Type | | :------------- | :------------- | | `artifactIds?` | `ArtifactId`[] | #### Returns (`undefined` \| `Artifact`)[] --- ### getBiomeKey ▸ `Private` **getBiomeKey**(`biome`): `string` #### Parameters | Name | Type | | :------ | :------ | | `biome` | `Biome` | #### Returns `string` --- ### getBiomePerlin ▸ **getBiomePerlin**(`coords`, `floor`): `number` #### Parameters | Name | Type | | :------- | :------------ | | `coords` | `WorldCoords` | | `floor` | `boolean` | #### Returns `number` --- ### getBooleanSetting ▸ **getBooleanSetting**(`setting`): `boolean` #### Parameters | Name | Type | | :-------- | :-------- | | `setting` | `Setting` | #### Returns `boolean` --- ### getCaptureZoneGenerator ▸ **getCaptureZoneGenerator**(): `undefined` \| [`CaptureZoneGenerator`](Backend_GameLogic_CaptureZoneGenerator.CaptureZoneGenerator.md) #### Returns `undefined` \| [`CaptureZoneGenerator`](Backend_GameLogic_CaptureZoneGenerator.CaptureZoneGenerator.md) --- ### getCaptureZonePointValues ▸ **getCaptureZonePointValues**(): [`number`, `number`, `number`, `number`, `number`, `number`, `number`, `number`, `number`, `number`] #### Returns [`number`, `number`, `number`, `number`, `number`, `number`, `number`, `number`, `number`, `number`] --- ### getCaptureZones ▸ **getCaptureZones**(): `Set`<`CaptureZone`\> #### Returns `Set`<`CaptureZone`\> --- ### getChunk ▸ **getChunk**(`chunkFootprint`): `undefined` \| `Chunk` #### Parameters | Name | Type | | :--------------- | :---------- | | `chunkFootprint` | `Rectangle` | #### Returns `undefined` \| `Chunk` --- ### getContractAddress ▸ **getContractAddress**(): `EthAddress` #### Returns `EthAddress` --- ### getDefaultSpaceJunkForPlanetLevel ▸ **getDefaultSpaceJunkForPlanetLevel**(`level`): `number` #### Parameters | Name | Type | | :------ | :------- | | `level` | `number` | #### Returns `number` --- ### getDiagnostics ▸ **getDiagnostics**(): `Diagnostics` #### Returns `Diagnostics` --- ### getDiscoverBiomeName ▸ **getDiscoverBiomeName**(`biome`): `string` #### Parameters | Name | Type | | :------ | :------ | | `biome` | `Biome` | #### Returns `string` --- ### getDistCoords ▸ **getDistCoords**(`from`, `to`): `number` #### Parameters | Name | Type | | :----- | :------------ | | `from` | `WorldCoords` | | `to` | `WorldCoords` | #### Returns `number` --- ### getEndTimeSeconds ▸ **getEndTimeSeconds**(): `number` #### Returns `number` --- ### getEnergyArrivingForMove ▸ **getEnergyArrivingForMove**(`from`, `to`, `dist`, `energy`): `number` #### Parameters | Name | Type | | :------- | :-------------------------- | | `from` | `LocationId` | | `to` | `undefined` \| `LocationId` | | `dist` | `undefined` \| `number` | | `energy` | `number` | #### Returns `number` --- ### getEnergyCurveAtPercent ▸ **getEnergyCurveAtPercent**(`planet`, `percent`): `number` #### Parameters | Name | Type | | :-------- | :------- | | `planet` | `Planet` | | `percent` | `number` | #### Returns `number` --- ### getEnergyOfPlayer ▸ **getEnergyOfPlayer**(`player`): `number` #### Parameters | Name | Type | | :------- | :----------- | | `player` | `EthAddress` | #### Returns `number` --- ### getEthConnection ▸ **getEthConnection**(): `EthConnection` #### Returns `EthConnection` --- ### getExploredChunks ▸ **getExploredChunks**(): `Iterable`<`Chunk`\> #### Returns `Iterable`<`Chunk`\> --- ### getForcesSending ▸ **getForcesSending**(`planetId?`): `number` Percent from 0 to 100. #### Parameters | Name | Type | | :---------- | :----------- | | `planetId?` | `LocationId` | #### Returns `number` --- ### getGameManager ▸ **getGameManager**(): [`default`](Backend_GameLogic_GameManager.default.md) #### Returns [`default`](Backend_GameLogic_GameManager.default.md) --- ### getGameObjects ▸ **getGameObjects**(): [`GameObjects`](Backend_GameLogic_GameObjects.GameObjects.md) Gets a reference to the game's internal representation of the world state. Beware! Use this for reading only, otherwise you might mess up the state of the game. You can try modifying the game state in some way #### Returns [`GameObjects`](Backend_GameLogic_GameObjects.GameObjects.md) --- ### getGlManager ▸ **getGlManager**(): `null` \| `GameGLManager` #### Returns `null` \| `GameGLManager` - A wrapper class for the WebGL2RenderingContext. --- ### getHashConfig ▸ **getHashConfig**(): [`HashConfig`](../modules/types_global_GlobalTypes.md#hashconfig) #### Returns [`HashConfig`](../modules/types_global_GlobalTypes.md#hashconfig) --- ### getHashesPerSec ▸ **getHashesPerSec**(): `number` #### Returns `number` --- ### getHomeCoords ▸ **getHomeCoords**(): `WorldCoords` #### Returns `WorldCoords` --- ### getHomeHash ▸ **getHomeHash**(): `undefined` \| `LocationId` #### Returns `undefined` \| `LocationId` --- ### getHomePlanet ▸ **getHomePlanet**(): `undefined` \| `Planet` #### Returns `undefined` \| `Planet` --- ### getHoveringOverCoords ▸ **getHoveringOverCoords**(): `undefined` \| `WorldCoords` #### Returns `undefined` \| `WorldCoords` --- ### getHoveringOverPlanet ▸ **getHoveringOverPlanet**(): `undefined` \| `Planet` #### Returns `undefined` \| `Planet` --- ### getIsChoosingTargetPlanet ▸ **getIsChoosingTargetPlanet**(): `boolean` #### Returns `boolean` --- ### getIsHighPerfMode ▸ **getIsHighPerfMode**(): `boolean` #### Returns `boolean` --- ### getLocationOfPlanet ▸ **getLocationOfPlanet**(`planetId`): `undefined` \| `WorldLocation` #### Parameters | Name | Type | | :--------- | :----------- | | `planetId` | `LocationId` | #### Returns `undefined` \| `WorldLocation` --- ### getLocationsAndChunks ▸ **getLocationsAndChunks**(): `Object` #### Returns `Object` | Name | Type | | :-------------- | :--------------------------------------- | | `cachedPlanets` | `Map`<`LocationId`, `PlanetRenderInfo`\> | | `chunks` | `Set`<`Chunk`\> | --- ### getMinerLocation ▸ **getMinerLocation**(): `undefined` \| `WorldCoords` #### Returns `undefined` \| `WorldCoords` --- ### getMiningPattern ▸ **getMiningPattern**(): `undefined` \| [`MiningPattern`](../interfaces/Backend_Miner_MiningPatterns.MiningPattern.md) #### Returns `undefined` \| [`MiningPattern`](../interfaces/Backend_Miner_MiningPatterns.MiningPattern.md) --- ### getModalManager ▸ **getModalManager**(): [`default`](Frontend_Game_ModalManager.default.md) #### Returns [`default`](Frontend_Game_ModalManager.default.md) --- ### getMouseDownCoords ▸ **getMouseDownCoords**(): `undefined` \| `WorldCoords` #### Returns `undefined` \| `WorldCoords` --- ### getMouseDownPlanet ▸ **getMouseDownPlanet**(): `undefined` \| `LocatablePlanet` #### Returns `undefined` \| `LocatablePlanet` --- ### getMyArtifactMap ▸ **getMyArtifactMap**(): `Map`<`ArtifactId`, `Artifact`\> #### Returns `Map`<`ArtifactId`, `Artifact`\> --- ### getMyArtifacts ▸ **getMyArtifacts**(): `Artifact`[] #### Returns `Artifact`[] --- ### getMyArtifactsNotOnPlanet ▸ **getMyArtifactsNotOnPlanet**(): `Artifact`[] #### Returns `Artifact`[] --- ### getMyBalance ▸ **getMyBalance**(): `number` #### Returns `number` --- ### getMyBalance$ ▸ **getMyBalance$**(): `Monomitter`<`BigNumber`\> #### Returns `Monomitter`<`BigNumber`\> --- ### getMyBalanceBn ▸ **getMyBalanceBn**(): `BigNumber` #### Returns `BigNumber` --- ### getMyPlanetMap ▸ **getMyPlanetMap**(): `Map`<`LocationId`, `Planet`\> #### Returns `Map`<`LocationId`, `Planet`\> --- ### getMyScore ▸ **getMyScore**(): `undefined` \| `number` #### Returns `undefined` \| `number` --- ### getNextBroadcastAvailableTimestamp ▸ **getNextBroadcastAvailableTimestamp**(): `number` #### Returns `number` --- ### getOverlayContainer ▸ **getOverlayContainer**(): `undefined` \| `HTMLDivElement` Gets the overlay container. See {@link GameUIManger.overlayContainer} for more information about what the overlay container is. #### Returns `undefined` \| `HTMLDivElement` --- ### getPaused ▸ **getPaused**(): `boolean` #### Returns `boolean` --- ### getPaused$ ▸ **getPaused$**(): `Monomitter`<`boolean`\> #### Returns `Monomitter`<`boolean`\> --- ### getPerlinConfig ▸ **getPerlinConfig**(`isBiome?`): `PerlinConfig` #### Parameters | Name | Type | Default value | | :-------- | :-------- | :------------ | | `isBiome` | `boolean` | `false` | #### Returns `PerlinConfig` --- ### getPerlinThresholds ▸ **getPerlinThresholds**(): [`number`, `number`, `number`] #### Returns [`number`, `number`, `number`] --- ### getPlanetHoveringInRenderer ▸ **getPlanetHoveringInRenderer**(): `boolean` If there is a planet being hovered over, returns whether or not it's being hovered over in the renderer. #### Returns `boolean` --- ### getPlanetLevel ▸ **getPlanetLevel**(`planetId`): `undefined` \| `PlanetLevel` #### Parameters | Name | Type | | :--------- | :----------- | | `planetId` | `LocationId` | #### Returns `undefined` \| `PlanetLevel` --- ### getPlanetMap ▸ **getPlanetMap**(): `Map`<`LocationId`, `Planet`\> #### Returns `Map`<`LocationId`, `Planet`\> --- ### getPlanetWithCoords ▸ **getPlanetWithCoords**(`coords`): `undefined` \| `Planet` #### Parameters | Name | Type | | :------- | :--------------------------- | | `coords` | `undefined` \| `WorldCoords` | #### Returns `undefined` \| `Planet` --- ### getPlanetWithId ▸ **getPlanetWithId**(`planetId`): `undefined` \| `Planet` #### Parameters | Name | Type | | :--------- | :-------------------------- | | `planetId` | `undefined` \| `LocationId` | #### Returns `undefined` \| `Planet` --- ### getPlanetsInViewport ▸ **getPlanetsInViewport**(): `Planet`[] #### Returns `Planet`[] --- ### getPlayer ▸ **getPlayer**(`address?`): `undefined` \| `Player` #### Parameters | Name | Type | | :--------- | :----------- | | `address?` | `EthAddress` | #### Returns `undefined` \| `Player` --- ### getPlayerScore ▸ **getPlayerScore**(`player`): `undefined` \| `number` #### Parameters | Name | Type | | :------- | :----------- | | `player` | `EthAddress` | #### Returns `undefined` \| `number` --- ### getPluginManager ▸ **getPluginManager**(): [`PluginManager`](Backend_GameLogic_PluginManager.PluginManager.md) #### Returns [`PluginManager`](Backend_GameLogic_PluginManager.PluginManager.md) --- ### getPreviousSelectedPlanet ▸ **getPreviousSelectedPlanet**(): `undefined` \| `Planet` #### Returns `undefined` \| `Planet` --- ### getPrivateKey ▸ **getPrivateKey**(): `undefined` \| `string` #### Returns `undefined` \| `string` --- ### getRadiusOfPlanetLevel ▸ **getRadiusOfPlanetLevel**(`planetRarity`): `number` #### Parameters | Name | Type | | :------------- | :------------ | | `planetRarity` | `PlanetLevel` | #### Returns `number` --- ### getRangeBuff ▸ **getRangeBuff**(): `number` #### Returns `number` --- ### getRenderer ▸ **getRenderer**(): `null` \| `Renderer` #### Returns `null` \| `Renderer` --- ### getSelectedCoords ▸ **getSelectedCoords**(): `undefined` \| `WorldCoords` #### Returns `undefined` \| `WorldCoords` --- ### getSelectedPlanet ▸ **getSelectedPlanet**(): `undefined` \| `LocatablePlanet` #### Returns `undefined` \| `LocatablePlanet` --- ### getSilverCurveAtPercent ▸ **getSilverCurveAtPercent**(`planet`, `percent`): `undefined` \| `number` #### Parameters | Name | Type | | :-------- | :------- | | `planet` | `Planet` | | `percent` | `number` | #### Returns `undefined` \| `number` --- ### getSilverOfPlayer ▸ **getSilverOfPlayer**(`player`): `number` #### Parameters | Name | Type | | :------- | :----------- | | `player` | `EthAddress` | #### Returns `number` --- ### getSilverScoreValue ▸ **getSilverScoreValue**(): `number` #### Returns `number` --- ### getSilverSending ▸ **getSilverSending**(`planetId?`): `number` Percent from 0 to 100. #### Parameters | Name | Type | | :---------- | :----------- | | `planetId?` | `LocationId` | #### Returns `number` --- ### getSpaceJunkEnabled ▸ **getSpaceJunkEnabled**(): `boolean` #### Returns `boolean` --- ### getSpaceTypePerlin ▸ **getSpaceTypePerlin**(`coords`, `floor`): `number` #### Parameters | Name | Type | | :------- | :------------ | | `coords` | `WorldCoords` | | `floor` | `boolean` | #### Returns `number` --- ### getSpeedBuff ▸ **getSpeedBuff**(): `number` #### Returns `number` --- ### getStringSetting ▸ **getStringSetting**(`setting`): `undefined` \| `string` #### Parameters | Name | Type | | :-------- | :-------- | | `setting` | `Setting` | #### Returns `undefined` \| `string` --- ### getTerminal ▸ **getTerminal**(): `undefined` \| [`TerminalHandle`](../interfaces/Frontend_Views_Terminal.TerminalHandle.md) #### Returns `undefined` \| [`TerminalHandle`](../interfaces/Frontend_Views_Terminal.TerminalHandle.md) --- ### getTwitter ▸ **getTwitter**(`address`): `undefined` \| `string` #### Parameters | Name | Type | | :-------- | :-------------------------- | | `address` | `undefined` \| `EthAddress` | #### Returns `undefined` \| `string` --- ### getUIEmitter ▸ **getUIEmitter**(): [`default`](Frontend_Utils_UIEmitter.default.md) #### Returns [`default`](Frontend_Utils_UIEmitter.default.md) --- ### getUnconfirmedMoves ▸ **getUnconfirmedMoves**(): `Transaction`<`UnconfirmedMove`\>[] **`todo`** delete this. now that [GameObjects](Backend_GameLogic_GameObjects.GameObjects.md) is publically accessible, we shouldn't need to drill fields like this anymore. **`tutorial`** Plugin developers, please access fields like this with something like {@code df.getGameObjects().} **`deprecated`** #### Returns `Transaction`<`UnconfirmedMove`\>[] --- ### getUnconfirmedUpgrades ▸ **getUnconfirmedUpgrades**(): `Transaction`<`UnconfirmedUpgrade`\>[] #### Returns `Transaction`<`UnconfirmedUpgrade`\>[] --- ### getUnconfirmedWormholeActivations ▸ **getUnconfirmedWormholeActivations**(): `Transaction`<`UnconfirmedActivateArtifact`\>[] #### Returns `Transaction`<`UnconfirmedActivateArtifact`\>[] --- ### getUniverseTotalEnergy ▸ **getUniverseTotalEnergy**(): `number` #### Returns `number` --- ### getUpgrade ▸ **getUpgrade**(`branch`, `level`): `Upgrade` #### Parameters | Name | Type | | :------- | :------------------ | | `branch` | `UpgradeBranchName` | | `level` | `number` | #### Returns `Upgrade` --- ### getViewport ▸ **getViewport**(): [`default`](Frontend_Game_Viewport.default.md) #### Returns [`default`](Frontend_Game_Viewport.default.md) --- ### getWorldRadius ▸ **getWorldRadius**(): `number` #### Returns `number` --- ### getWorldSilver ▸ **getWorldSilver**(): `number` #### Returns `number` --- ### getWormholes ▸ **getWormholes**(): `Iterable`<`Wormhole`\> #### Returns `Iterable`<`Wormhole`\> --- ### hasMinedChunk ▸ **hasMinedChunk**(`chunkLocation`): `boolean` #### Parameters | Name | Type | | :-------------- | :---------- | | `chunkLocation` | `Rectangle` | #### Returns `boolean` --- ### isAbandoning ▸ **isAbandoning**(): `boolean` #### Returns `boolean` --- ### isAdmin ▸ **isAdmin**(): `boolean` #### Returns `boolean` --- ### isCurrentlyRevealing ▸ **isCurrentlyRevealing**(): `boolean` #### Returns `boolean` --- ### isMining ▸ **isMining**(): `boolean` #### Returns `boolean` --- ### isOverOwnPlanet ▸ **isOverOwnPlanet**(`coords`): `undefined` \| `Planet` #### Parameters | Name | Type | | :------- | :------------ | | `coords` | `WorldCoords` | #### Returns `undefined` \| `Planet` --- ### isOwnedByMe ▸ **isOwnedByMe**(`planet`): `boolean` #### Parameters | Name | Type | | :------- | :------- | | `planet` | `Planet` | #### Returns `boolean` --- ### isRoundOver ▸ **isRoundOver**(): `boolean` #### Returns `boolean` --- ### isSendingForces ▸ **isSendingForces**(): `boolean` #### Returns `boolean` --- ### isSendingShip ▸ **isSendingShip**(`planetId?`): `boolean` #### Parameters | Name | Type | | :---------- | :----------- | | `planetId?` | `LocationId` | #### Returns `boolean` --- ### joinGame ▸ **joinGame**(`beforeRetry`): `Promise`<`void`\> #### Parameters | Name | Type | | :------------ | :-------------------------------------- | | `beforeRetry` | (`e`: `Error`) => `Promise`<`boolean`\> | #### Returns `Promise`<`void`\> --- ### onDiscoveredChunk ▸ **onDiscoveredChunk**(`chunk`): `void` #### Parameters | Name | Type | | :------ | :------ | | `chunk` | `Chunk` | #### Returns `void` --- ### onEmitInitializedPlayer ▸ `Private` **onEmitInitializedPlayer**(): `void` #### Returns `void` --- ### onEmitInitializedPlayerError ▸ `Private` **onEmitInitializedPlayerError**(`err`): `void` #### Parameters | Name | Type | | :---- | :---------- | | `err` | `ReactNode` | #### Returns `void` --- ### onMouseClick ▸ **onMouseClick**(`_coords`): `void` #### Parameters | Name | Type | | :-------- | :------------ | | `_coords` | `WorldCoords` | #### Returns `void` --- ### onMouseDown ▸ **onMouseDown**(`coords`): `void` #### Parameters | Name | Type | | :------- | :------------ | | `coords` | `WorldCoords` | #### Returns `void` --- ### onMouseMove ▸ **onMouseMove**(`coords`): `void` #### Parameters | Name | Type | | :------- | :------------ | | `coords` | `WorldCoords` | #### Returns `void` --- ### onMouseOut ▸ **onMouseOut**(): `void` #### Returns `void` --- ### onMouseUp ▸ **onMouseUp**(`coords`): `void` #### Parameters | Name | Type | | :------- | :------------ | | `coords` | `WorldCoords` | #### Returns `void` --- ### onSendCancel ▸ **onSendCancel**(): `void` #### Returns `void` --- ### onSendComplete ▸ **onSendComplete**(`locationId`): `void` #### Parameters | Name | Type | | :----------- | :----------- | | `locationId` | `LocationId` | #### Returns `void` --- ### onSendInit ▸ **onSendInit**(`planet`): `void` #### Parameters | Name | Type | | :------- | :------------------------------- | | `planet` | `undefined` \| `LocatablePlanet` | #### Returns `void` --- ### potentialCaptureScore ▸ **potentialCaptureScore**(`planetLevel`): `number` #### Parameters | Name | Type | | :------------ | :------- | | `planetLevel` | `number` | #### Returns `number` --- ### prospectPlanet ▸ **prospectPlanet**(`planetId`): `void` #### Parameters | Name | Type | | :--------- | :----------- | | `planetId` | `LocationId` | #### Returns `void` --- ### removeExtraMinerLocation ▸ **removeExtraMinerLocation**(`idx`): `void` #### Parameters | Name | Type | | :---- | :------- | | `idx` | `number` | #### Returns `void` --- ### revealLocation ▸ **revealLocation**(`locationId`): `void` #### Parameters | Name | Type | | :----------- | :----------- | | `locationId` | `LocationId` | #### Returns `void` --- ### setAbandoning ▸ **setAbandoning**(`abandoning`): `void` #### Parameters | Name | Type | | :----------- | :-------- | | `abandoning` | `boolean` | #### Returns `void` --- ### setArtifactSending ▸ **setArtifactSending**(`planetId`, `artifact?`): `void` #### Parameters | Name | Type | | :---------- | :----------- | | `planetId` | `LocationId` | | `artifact?` | `Artifact` | #### Returns `void` --- ### setCustomRenderer ▸ **setCustomRenderer**(`customRenderer`): `void` Replaces the current renderer with the passed in custom renderer and adds the renderer to the rendering stack. The function will automatically determine which renderer it is by the rendererType and the methods in the renderer. #### Parameters | Name | Type | Description | | :--------------- | :------------- | :------------------------------------------------------- | | `customRenderer` | `BaseRenderer` | a Renderer that follows one of the 23 renderer tempaltes | #### Returns `void` --- ### setExtraMinerLocation ▸ **setExtraMinerLocation**(`idx`, `coords`): `void` #### Parameters | Name | Type | | :------- | :------------ | | `idx` | `number` | | `coords` | `WorldCoords` | #### Returns `void` --- ### setForcesSending ▸ **setForcesSending**(`planetId`, `percentage`): `void` #### Parameters | Name | Type | | :----------- | :----------- | | `planetId` | `LocationId` | | `percentage` | `number` | #### Returns `void` --- ### setHoveringOverArtifact ▸ **setHoveringOverArtifact**(`artifactId?`): `void` #### Parameters | Name | Type | | :------------ | :----------- | | `artifactId?` | `ArtifactId` | #### Returns `void` --- ### setHoveringOverPlanet ▸ **setHoveringOverPlanet**(`planet`, `inRenderer`): `void` #### Parameters | Name | Type | | :----------- | :------------------------------- | | `planet` | `undefined` \| `LocatablePlanet` | | `inRenderer` | `boolean` | #### Returns `void` --- ### setMiningPattern ▸ **setMiningPattern**(`pattern`): `void` #### Parameters | Name | Type | | :-------- | :----------------------------------------------------------------------------- | | `pattern` | [`MiningPattern`](../interfaces/Backend_Miner_MiningPatterns.MiningPattern.md) | #### Returns `void` --- ### setModalManager ▸ `Private` **setModalManager**(`modalManager`): `void` #### Parameters | Name | Type | | :------------- | :------------------------------------------------- | | `modalManager` | [`default`](Frontend_Game_ModalManager.default.md) | #### Returns `void` --- ### setOverlayContainer ▸ **setOverlayContainer**(`randomContainer?`): `void` Sets the overlay container. See {@link GameUIManger.overlayContainer} for more information about what the overlay container is. #### Parameters | Name | Type | | :----------------- | :--------------- | | `randomContainer?` | `HTMLDivElement` | #### Returns `void` --- ### setSelectedId ▸ **setSelectedId**(`id`): `void` #### Parameters | Name | Type | | :--- | :----------- | | `id` | `LocationId` | #### Returns `void` --- ### setSelectedPlanet ▸ **setSelectedPlanet**(`planet`): `void` #### Parameters | Name | Type | | :------- | :------------------------------- | | `planet` | `undefined` \| `LocatablePlanet` | #### Returns `void` --- ### setSending ▸ **setSending**(`sending`): `void` #### Parameters | Name | Type | | :-------- | :-------- | | `sending` | `boolean` | #### Returns `void` --- ### setSilverSending ▸ **setSilverSending**(`planetId`, `percentage`): `void` #### Parameters | Name | Type | | :----------- | :----------- | | `planetId` | `LocationId` | | `percentage` | `number` | #### Returns `void` --- ### spaceTypeFromPerlin ▸ **spaceTypeFromPerlin**(`perlin`): `SpaceType` #### Parameters | Name | Type | | :------- | :------- | | `perlin` | `number` | #### Returns `SpaceType` --- ### startExplore ▸ **startExplore**(): `void` #### Returns `void` --- ### startWormholeFrom ▸ **startWormholeFrom**(`planet`): `Promise`<`undefined` \| `LocatablePlanet`\> #### Parameters | Name | Type | | :------- | :---------------- | | `planet` | `LocatablePlanet` | #### Returns `Promise`<`undefined` \| `LocatablePlanet`\> --- ### stopExplore ▸ **stopExplore**(): `void` #### Returns `void` --- ### timeUntilNextBroadcastAvailable ▸ **timeUntilNextBroadcastAvailable**(): `number` #### Returns `number` --- ### toggleExplore ▸ **toggleExplore**(): `void` #### Returns `void` --- ### toggleTargettingExplorer ▸ **toggleTargettingExplorer**(): `void` #### Returns `void` --- ### updateDiagnostics ▸ **updateDiagnostics**(`updateFn`): `void` #### Parameters | Name | Type | | :--------- | :----------------------------- | | `updateFn` | (`d`: `Diagnostics`) => `void` | #### Returns `void` --- ### updateMouseHoveringOverCoords ▸ `Private` **updateMouseHoveringOverCoords**(`coords`): `WorldCoords` #### Parameters | Name | Type | | :------- | :------------ | | `coords` | `WorldCoords` | #### Returns `WorldCoords` --- ### updatePlanets ▸ `Private` **updatePlanets**(): `void` #### Returns `void` --- ### upgrade ▸ **upgrade**(`planet`, `branch`): `void` #### Parameters | Name | Type | | :------- | :------- | | `planet` | `Planet` | | `branch` | `number` | #### Returns `void` --- ### verifyTwitter ▸ **verifyTwitter**(`twitter`): `Promise`<`boolean`\> #### Parameters | Name | Type | | :-------- | :------- | | `twitter` | `string` | #### Returns `Promise`<`boolean`\> --- ### withdrawArtifact ▸ **withdrawArtifact**(`locationId`, `artifactId`): `void` #### Parameters | Name | Type | | :----------- | :----------- | | `locationId` | `LocationId` | | `artifactId` | `ArtifactId` | #### Returns `void` --- ### withdrawSilver ▸ **withdrawSilver**(`locationId`, `amount`): `void` #### Parameters | Name | Type | | :----------- | :----------- | | `locationId` | `LocationId` | | `amount` | `number` | #### Returns `void` --- ### create ▸ `Static` **create**(`gameManager`, `terminalHandle`): `Promise`<[`default`](Backend_GameLogic_GameUIManager.default.md)\> #### Parameters | Name | Type | | :--------------- | :-------------------------------------------------------------------------------------------------------------- | | `gameManager` | [`default`](Backend_GameLogic_GameManager.default.md) | | `terminalHandle` | `MutableRefObject`<`undefined` \| [`TerminalHandle`](../interfaces/Frontend_Views_Terminal.TerminalHandle.md)\> | #### Returns `Promise`<[`default`](Backend_GameLogic_GameUIManager.default.md)\> ================================================ FILE: docs/classes/Backend_GameLogic_InitialGameStateDownloader.InitialGameStateDownloader.md ================================================ # Class: InitialGameStateDownloader [Backend/GameLogic/InitialGameStateDownloader](../modules/Backend_GameLogic_InitialGameStateDownloader.md).InitialGameStateDownloader ## Table of contents ### Constructors - [constructor](Backend_GameLogic_InitialGameStateDownloader.InitialGameStateDownloader.md#constructor) ### Properties - [terminal](Backend_GameLogic_InitialGameStateDownloader.InitialGameStateDownloader.md#terminal) ### Methods - [download](Backend_GameLogic_InitialGameStateDownloader.InitialGameStateDownloader.md#download) - [makeProgressListener](Backend_GameLogic_InitialGameStateDownloader.InitialGameStateDownloader.md#makeprogresslistener) ## Constructors ### constructor • **new InitialGameStateDownloader**(`terminal`) #### Parameters | Name | Type | | :--------- | :-------------------------------------------------------------------------- | | `terminal` | [`TerminalHandle`](../interfaces/Frontend_Views_Terminal.TerminalHandle.md) | ## Properties ### terminal • `Private` **terminal**: [`TerminalHandle`](../interfaces/Frontend_Views_Terminal.TerminalHandle.md) ## Methods ### download ▸ **download**(`contractsAPI`, `persistentChunkStore`): `Promise`<[`InitialGameState`](../interfaces/Backend_GameLogic_InitialGameStateDownloader.InitialGameState.md)\> #### Parameters | Name | Type | | :--------------------- | :--------------------------------------------------------------- | | `contractsAPI` | [`ContractsAPI`](Backend_GameLogic_ContractsAPI.ContractsAPI.md) | | `persistentChunkStore` | [`default`](Backend_Storage_PersistentChunkStore.default.md) | #### Returns `Promise`<[`InitialGameState`](../interfaces/Backend_GameLogic_InitialGameStateDownloader.InitialGameState.md)\> --- ### makeProgressListener ▸ `Private` **makeProgressListener**(`prettyEntityName`): (`percent`: `number`) => `void` #### Parameters | Name | Type | | :----------------- | :------- | | `prettyEntityName` | `string` | #### Returns `fn` ▸ (`percent`): `void` ##### Parameters | Name | Type | | :-------- | :------- | | `percent` | `number` | ##### Returns `void` ================================================ FILE: docs/classes/Backend_GameLogic_LayeredMap.LayeredMap.md ================================================ # Class: LayeredMap [Backend/GameLogic/LayeredMap](../modules/Backend_GameLogic_LayeredMap.md).LayeredMap Data structure which allows us to efficiently query for "which planets between level X and X + n (for positive n) are present in the given world rectangle", as well as, in the future, "which chunks are visible in the vieport". ## Table of contents ### Constructors - [constructor](Backend_GameLogic_LayeredMap.LayeredMap.md#constructor) ### Properties - [insertedLocations](Backend_GameLogic_LayeredMap.LayeredMap.md#insertedlocations) - [perLevelPlanetQuadtrees](Backend_GameLogic_LayeredMap.LayeredMap.md#perlevelplanetquadtrees) ### Methods - [getPlanets](Backend_GameLogic_LayeredMap.LayeredMap.md#getplanets) - [getPlanetsInCircle](Backend_GameLogic_LayeredMap.LayeredMap.md#getplanetsincircle) - [getPointLocationId](Backend_GameLogic_LayeredMap.LayeredMap.md#getpointlocationid) - [insertPlanet](Backend_GameLogic_LayeredMap.LayeredMap.md#insertplanet) ## Constructors ### constructor • **new LayeredMap**(`worldRadius`) #### Parameters | Name | Type | | :------------ | :------- | | `worldRadius` | `number` | ## Properties ### insertedLocations • `Private` **insertedLocations**: `Set`<`LocationId`\> --- ### perLevelPlanetQuadtrees • `Private` **perLevelPlanetQuadtrees**: `Map`<`number`, `QuadTree`\> ## Methods ### getPlanets ▸ **getPlanets**(`worldX`, `worldY`, `worldWidth`, `worldHeight`, `planetLevels`, `planetLevelToRadii`): `LocationId`[] Gets the ids of all the planets that are both within the given bounding box (defined by its bottom left coordinate, width, and height) in the world and of a level that was passed in via the `planetLevels` parameter. #### Parameters | Name | Type | | :------------------- | :------------------------ | | `worldX` | `number` | | `worldY` | `number` | | `worldWidth` | `number` | | `worldHeight` | `number` | | `planetLevels` | `number`[] | | `planetLevelToRadii` | `Map`<`number`, `Radii`\> | #### Returns `LocationId`[] --- ### getPlanetsInCircle ▸ **getPlanetsInCircle**(`coords`, `worldRadius`): `LocationId`[] Gets all the planets within the given world radius of a world location. #### Parameters | Name | Type | | :------------ | :------------ | | `coords` | `WorldCoords` | | `worldRadius` | `number` | #### Returns `LocationId`[] --- ### getPointLocationId ▸ `Private` **getPointLocationId**(`point`): `LocationId` #### Parameters | Name | Type | | :------ | :------ | | `point` | `Point` | #### Returns `LocationId` --- ### insertPlanet ▸ **insertPlanet**(`location`, `planetLevel`): `void` Records the fact that there is a planet at the given world location. #### Parameters | Name | Type | | :------------ | :-------------- | | `location` | `WorldLocation` | | `planetLevel` | `number` | #### Returns `void` ================================================ FILE: docs/classes/Backend_GameLogic_PluginManager.PluginManager.md ================================================ # Class: PluginManager [Backend/GameLogic/PluginManager](../modules/Backend_GameLogic_PluginManager.md).PluginManager This class keeps track of all the plugins that this player has loaded into their game. Acts as a task manager, supports all CRUD operations for plugins, as well as instantiating and destroying running plugins. All library operations are also persisted to IndexDB. Important! Does not run plugins until the user clicks 'run' somewhere in this UI. This is important, because if someone develops a buggy plugin, it would suck if that bricked their game. ## Table of contents ### Constructors - [constructor](Backend_GameLogic_PluginManager.PluginManager.md#constructor) ### Properties - [gameManager](Backend_GameLogic_PluginManager.PluginManager.md#gamemanager) - [pluginLibrary](Backend_GameLogic_PluginManager.PluginManager.md#pluginlibrary) - [pluginProcessInfos](Backend_GameLogic_PluginManager.PluginManager.md#pluginprocessinfos) - [pluginProcesses](Backend_GameLogic_PluginManager.PluginManager.md#pluginprocesses) - [plugins$](Backend_GameLogic_PluginManager.PluginManager.md#plugins$) ### Methods - [addPluginToLibrary](Backend_GameLogic_PluginManager.PluginManager.md#addplugintolibrary) - [deletePlugin](Backend_GameLogic_PluginManager.PluginManager.md#deleteplugin) - [destroy](Backend_GameLogic_PluginManager.PluginManager.md#destroy) - [drawAllRunningPlugins](Backend_GameLogic_PluginManager.PluginManager.md#drawallrunningplugins) - [getAllProcessInfos](Backend_GameLogic_PluginManager.PluginManager.md#getallprocessinfos) - [getLibrary](Backend_GameLogic_PluginManager.PluginManager.md#getlibrary) - [getPluginFromLibrary](Backend_GameLogic_PluginManager.PluginManager.md#getpluginfromlibrary) - [getProcessInfo](Backend_GameLogic_PluginManager.PluginManager.md#getprocessinfo) - [hasPlugin](Backend_GameLogic_PluginManager.PluginManager.md#hasplugin) - [load](Backend_GameLogic_PluginManager.PluginManager.md#load) - [notifyPluginLibraryUpdated](Backend_GameLogic_PluginManager.PluginManager.md#notifypluginlibraryupdated) - [onNewEmbeddedPlugins](Backend_GameLogic_PluginManager.PluginManager.md#onnewembeddedplugins) - [overwritePlugin](Backend_GameLogic_PluginManager.PluginManager.md#overwriteplugin) - [render](Backend_GameLogic_PluginManager.PluginManager.md#render) - [reorderPlugins](Backend_GameLogic_PluginManager.PluginManager.md#reorderplugins) - [spawn](Backend_GameLogic_PluginManager.PluginManager.md#spawn) - [copy](Backend_GameLogic_PluginManager.PluginManager.md#copy) ## Constructors ### constructor • **new PluginManager**(`gameManager`) #### Parameters | Name | Type | | :------------ | :---------------------------------------------------- | | `gameManager` | [`default`](Backend_GameLogic_GameManager.default.md) | ## Properties ### gameManager • `Private` **gameManager**: [`default`](Backend_GameLogic_GameManager.default.md) --- ### pluginLibrary • `Private` **pluginLibrary**: [`SerializedPlugin`](../interfaces/Backend_Plugins_SerializedPlugin.SerializedPlugin.md)[] All the plugins in the player's library. Not all of the player's plugins are running, and therefore not all exist in `pluginInstances`. `PluginsManager` keeps this field in sync with the plugins the user has saved in the IndexDB via {@link PersistentChunkStore} --- ### pluginProcessInfos • `Private` **pluginProcessInfos**: `Record`<`string`, [`ProcessInfo`](Backend_GameLogic_PluginManager.ProcessInfo.md)\> parallel to pluginProcesses --- ### pluginProcesses • `Private` **pluginProcesses**: `Record`<`string`, [`PluginProcess`](../interfaces/Backend_Plugins_PluginProcess.PluginProcess.md)\> Plugins that are currently loaded into the game, and are rendering into a modal. `PluginsManager` makes sure that when a plugin starts executing, it is added into `pluginInstances`, and that once a plugin is unloaded, its `.destroy()` method is called, and that the plugin is removed from `pluginInstances`. --- ### plugins$ • **plugins$**: `Monomitter`<[`SerializedPlugin`](../interfaces/Backend_Plugins_SerializedPlugin.SerializedPlugin.md)[]\> Event emitter that publishes whenever the set of plugins changes. ## Methods ### addPluginToLibrary ▸ **addPluginToLibrary**(`id`, `name`, `code`): [`SerializedPlugin`](../interfaces/Backend_Plugins_SerializedPlugin.SerializedPlugin.md) adds a new plugin into the plugin library. #### Parameters | Name | Type | | :----- | :--------- | | `id` | `PluginId` | | `name` | `string` | | `code` | `string` | #### Returns [`SerializedPlugin`](../interfaces/Backend_Plugins_SerializedPlugin.SerializedPlugin.md) --- ### deletePlugin ▸ **deletePlugin**(`pluginId`): `Promise`<`void`\> Remove the given plugin both from the player's library, and kills the plugin if it is running. #### Parameters | Name | Type | | :--------- | :--------- | | `pluginId` | `PluginId` | #### Returns `Promise`<`void`\> --- ### destroy ▸ **destroy**(`id`): `void` If a plugin with the given id is running, call its `.destroy()` method, and remove it from `pluginInstances`. Stop listening for new local plugins. #### Parameters | Name | Type | | :--- | :--------- | | `id` | `PluginId` | #### Returns `void` --- ### drawAllRunningPlugins ▸ **drawAllRunningPlugins**(`ctx`): `void` For each currently running plugin, if the plugin has a 'draw' function, then draw that plugin to the screen. #### Parameters | Name | Type | | :---- | :------------------------- | | `ctx` | `CanvasRenderingContext2D` | #### Returns `void` --- ### getAllProcessInfos ▸ **getAllProcessInfos**(): `Map`<`PluginId`, [`ProcessInfo`](Backend_GameLogic_PluginManager.ProcessInfo.md)\> Gets a map of all the currently running processes #### Returns `Map`<`PluginId`, [`ProcessInfo`](Backend_GameLogic_PluginManager.ProcessInfo.md)\> --- ### getLibrary ▸ **getLibrary**(): [`SerializedPlugin`](../interfaces/Backend_Plugins_SerializedPlugin.SerializedPlugin.md)[] Gets all the plugins in this player's library. #### Returns [`SerializedPlugin`](../interfaces/Backend_Plugins_SerializedPlugin.SerializedPlugin.md)[] --- ### getPluginFromLibrary ▸ **getPluginFromLibrary**(`id?`): `undefined` \| [`SerializedPlugin`](../interfaces/Backend_Plugins_SerializedPlugin.SerializedPlugin.md) Gets the serialized plugin with the given id from the player's plugin library. `undefined` if no plugin exists. #### Parameters | Name | Type | | :---- | :--------- | | `id?` | `PluginId` | #### Returns `undefined` \| [`SerializedPlugin`](../interfaces/Backend_Plugins_SerializedPlugin.SerializedPlugin.md) --- ### getProcessInfo ▸ **getProcessInfo**(`id`): [`ProcessInfo`](Backend_GameLogic_PluginManager.ProcessInfo.md) If this process has been started, gets its info #### Parameters | Name | Type | | :--- | :--------- | | `id` | `PluginId` | #### Returns [`ProcessInfo`](Backend_GameLogic_PluginManager.ProcessInfo.md) --- ### hasPlugin ▸ `Private` **hasPlugin**(`plugin`): `boolean` #### Parameters | Name | Type | | :------- | :--------------------------------------------------------------------------------------- | | `plugin` | [`EmbeddedPlugin`](../interfaces/Backend_Plugins_EmbeddedPluginLoader.EmbeddedPlugin.md) | #### Returns `boolean` --- ### load ▸ **load**(`isAdmin`, `overwriteEmbeddedPlugins`): `Promise`<`void`\> Load all plugins from this disk into `pluginLibrary`. Insert the default plugins into the player's library if the default plugins have never been added before. Effectively idempotent after the first time you call it. #### Parameters | Name | Type | Description | | :------------------------- | :-------- | :---------------------------------------------------------------------------------------- | | `isAdmin` | `boolean` | Is an admin loading the plugins. | | `overwriteEmbeddedPlugins` | `boolean` | Reload all embedded plugins even if a local copy is found. Useful for plugin development. | #### Returns `Promise`<`void`\> --- ### notifyPluginLibraryUpdated ▸ `Private` **notifyPluginLibraryUpdated**(): `void` #### Returns `void` --- ### onNewEmbeddedPlugins ▸ `Private` **onNewEmbeddedPlugins**(`newPlugins`, `overwriteEmbeddedPlugins`): `void` #### Parameters | Name | Type | | :------------------------- | :----------------------------------------------------------------------------------------- | | `newPlugins` | [`EmbeddedPlugin`](../interfaces/Backend_Plugins_EmbeddedPluginLoader.EmbeddedPlugin.md)[] | | `overwriteEmbeddedPlugins` | `boolean` | #### Returns `void` --- ### overwritePlugin ▸ **overwritePlugin**(`newName`, `pluginCode`, `id`): `void` 1. kills the plugin if it's running 2. edits the plugin-library version of this plugin 3. if a plugin was edited, save the plugin library to disk #### Parameters | Name | Type | | :----------- | :--------- | | `newName` | `string` | | `pluginCode` | `string` | | `id` | `PluginId` | #### Returns `void` --- ### render ▸ **render**(`id`, `element`): `Promise`<`void`\> If this plugin's `render` method has not been called yet, then call it! Remembers that this plugin has been rendered. #### Parameters | Name | Type | | :-------- | :--------------- | | `id` | `PluginId` | | `element` | `HTMLDivElement` | #### Returns `Promise`<`void`\> --- ### reorderPlugins ▸ **reorderPlugins**(`newPluginIdOrder`): `void` Reorders the current plugins. plugin ids in `newPluginIdOrder` must correspond 1:1 to plugins in the plugin library. #### Parameters | Name | Type | | :----------------- | :--------- | | `newPluginIdOrder` | `string`[] | #### Returns `void` --- ### spawn ▸ **spawn**(`id`): `Promise`<`undefined` \| [`PluginProcess`](../interfaces/Backend_Plugins_PluginProcess.PluginProcess.md)\> Either spawns the given plugin by evaluating its `pluginCode`, or returns the already running plugin instance. If starting a plugin throws an error then returns `undefined`. #### Parameters | Name | Type | | :--- | :--------- | | `id` | `PluginId` | #### Returns `Promise`<`undefined` \| [`PluginProcess`](../interfaces/Backend_Plugins_PluginProcess.PluginProcess.md)\> --- ### copy ▸ `Static` `Private` **copy**<`T`\>(`plugin`): `T` To prevent users of this class from modifying our plugins library, we return clones of the plugins. This should probably be a function in a Utils file somewhere, but I thought I should leave a good comment about why we return copies of the plugins from the library. #### Type parameters | Name | | :--- | | `T` | #### Parameters | Name | Type | | :------- | :--- | | `plugin` | `T` | #### Returns `T` ================================================ FILE: docs/classes/Backend_GameLogic_PluginManager.ProcessInfo.md ================================================ # Class: ProcessInfo [Backend/GameLogic/PluginManager](../modules/Backend_GameLogic_PluginManager.md).ProcessInfo Represents book-keeping information about a running process. We keep it separate from the process code, so that the plugin doesn't accidentally overwrite this information. ## Table of contents ### Constructors - [constructor](Backend_GameLogic_PluginManager.ProcessInfo.md#constructor) ### Properties - [hasError](Backend_GameLogic_PluginManager.ProcessInfo.md#haserror) - [rendered](Backend_GameLogic_PluginManager.ProcessInfo.md#rendered) ## Constructors ### constructor • **new ProcessInfo**() ## Properties ### hasError • **hasError**: `boolean` = `false` --- ### rendered • **rendered**: `boolean` = `false` ================================================ FILE: docs/classes/Backend_GameLogic_TutorialManager.default.md ================================================ # Class: default [Backend/GameLogic/TutorialManager](../modules/Backend_GameLogic_TutorialManager.md).default ## Hierarchy - `EventEmitter` ↳ **`default`** ## Table of contents ### Constructors - [constructor](Backend_GameLogic_TutorialManager.default.md#constructor) ### Properties - [tutorialState](Backend_GameLogic_TutorialManager.default.md#tutorialstate) - [uiManager](Backend_GameLogic_TutorialManager.default.md#uimanager) - [instance](Backend_GameLogic_TutorialManager.default.md#instance) ### Methods - [acceptInput](Backend_GameLogic_TutorialManager.default.md#acceptinput) - [advance](Backend_GameLogic_TutorialManager.default.md#advance) - [complete](Backend_GameLogic_TutorialManager.default.md#complete) - [reset](Backend_GameLogic_TutorialManager.default.md#reset) - [setTutorialState](Backend_GameLogic_TutorialManager.default.md#settutorialstate) - [shouldSkipState](Backend_GameLogic_TutorialManager.default.md#shouldskipstate) - [getInstance](Backend_GameLogic_TutorialManager.default.md#getinstance) ## Constructors ### constructor • `Private` **new default**(`uiManager`) #### Parameters | Name | Type | | :---------- | :------------------------------------------------------ | | `uiManager` | [`default`](Backend_GameLogic_GameUIManager.default.md) | #### Overrides EventEmitter.constructor ## Properties ### tutorialState • `Private` **tutorialState**: [`TutorialState`](../enums/Backend_GameLogic_TutorialManager.TutorialState.md) = `TutorialState.None` --- ### uiManager • `Private` **uiManager**: [`default`](Backend_GameLogic_GameUIManager.default.md) --- ### instance ▪ `Static` **instance**: [`default`](Backend_GameLogic_TutorialManager.default.md) ## Methods ### acceptInput ▸ **acceptInput**(`state`): `void` #### Parameters | Name | Type | | :------ | :----------------------------------------------------------------------------- | | `state` | [`TutorialState`](../enums/Backend_GameLogic_TutorialManager.TutorialState.md) | #### Returns `void` --- ### advance ▸ `Private` **advance**(): `void` #### Returns `void` --- ### complete ▸ **complete**(): `void` #### Returns `void` --- ### reset ▸ **reset**(): `void` #### Returns `void` --- ### setTutorialState ▸ `Private` **setTutorialState**(`newState`): `void` #### Parameters | Name | Type | | :--------- | :----------------------------------------------------------------------------- | | `newState` | [`TutorialState`](../enums/Backend_GameLogic_TutorialManager.TutorialState.md) | #### Returns `void` --- ### shouldSkipState ▸ `Private` **shouldSkipState**(`state`): `boolean` #### Parameters | Name | Type | | :------ | :----------------------------------------------------------------------------- | | `state` | [`TutorialState`](../enums/Backend_GameLogic_TutorialManager.TutorialState.md) | #### Returns `boolean` --- ### getInstance ▸ `Static` **getInstance**(`uiManager`): [`default`](Backend_GameLogic_TutorialManager.default.md) #### Parameters | Name | Type | | :---------- | :------------------------------------------------------ | | `uiManager` | [`default`](Backend_GameLogic_GameUIManager.default.md) | #### Returns [`default`](Backend_GameLogic_TutorialManager.default.md) ================================================ FILE: docs/classes/Backend_GameLogic_ViewportEntities.ViewportEntities.md ================================================ # Class: ViewportEntities [Backend/GameLogic/ViewportEntities](../modules/Backend_GameLogic_ViewportEntities.md).ViewportEntities Efficiently calculates which planets are in the viewport, and allows you to find the nearest visible planet to the mouse. ## Table of contents ### Constructors - [constructor](Backend_GameLogic_ViewportEntities.ViewportEntities.md#constructor) ### Properties - [cachedExploredChunks](Backend_GameLogic_ViewportEntities.ViewportEntities.md#cachedexploredchunks) - [cachedPlanets](Backend_GameLogic_ViewportEntities.ViewportEntities.md#cachedplanets) - [gameManager](Backend_GameLogic_ViewportEntities.ViewportEntities.md#gamemanager) - [uiManager](Backend_GameLogic_ViewportEntities.ViewportEntities.md#uimanager) ### Methods - [getNearestVisiblePlanet](Backend_GameLogic_ViewportEntities.ViewportEntities.md#getnearestvisibleplanet) - [getPlanetRadii](Backend_GameLogic_ViewportEntities.ViewportEntities.md#getplanetradii) - [getPlanetsAndChunks](Backend_GameLogic_ViewportEntities.ViewportEntities.md#getplanetsandchunks) - [getVisiblePlanetLevels](Backend_GameLogic_ViewportEntities.ViewportEntities.md#getvisibleplanetlevels) - [loadPlanetMessages](Backend_GameLogic_ViewportEntities.ViewportEntities.md#loadplanetmessages) - [recalculateViewportChunks](Backend_GameLogic_ViewportEntities.ViewportEntities.md#recalculateviewportchunks) - [recalculateViewportPlanets](Backend_GameLogic_ViewportEntities.ViewportEntities.md#recalculateviewportplanets) - [replacePlanets](Backend_GameLogic_ViewportEntities.ViewportEntities.md#replaceplanets) - [startRefreshing](Backend_GameLogic_ViewportEntities.ViewportEntities.md#startrefreshing) - [updateLocationsAndChunks](Backend_GameLogic_ViewportEntities.ViewportEntities.md#updatelocationsandchunks) ## Constructors ### constructor • **new ViewportEntities**(`gameManager`, `gameUIManager`) #### Parameters | Name | Type | | :-------------- | :------------------------------------------------------ | | `gameManager` | [`default`](Backend_GameLogic_GameManager.default.md) | | `gameUIManager` | [`default`](Backend_GameLogic_GameUIManager.default.md) | ## Properties ### cachedExploredChunks • `Private` **cachedExploredChunks**: `Set`<`Chunk`\> --- ### cachedPlanets • `Private` **cachedPlanets**: `Map`<`LocationId`, `PlanetRenderInfo`\> --- ### gameManager • `Private` `Readonly` **gameManager**: [`default`](Backend_GameLogic_GameManager.default.md) --- ### uiManager • `Private` `Readonly` **uiManager**: [`default`](Backend_GameLogic_GameUIManager.default.md) ## Methods ### getNearestVisiblePlanet ▸ **getNearestVisiblePlanet**(`coords`): `undefined` \| `LocatablePlanet` Gets the planet that is closest to the given coordinates. Filters out irrelevant planets using the `radiusMap` parameter, which specifies how close a planet must be in order to be returned from this function, given that planet's level. Smaller planets have a smaller radius, and larger planets have a larger radius. If a smaller and a larger planet are both within respective radii of coords, the smaller planet is returned. #### Parameters | Name | Type | | :------- | :------------ | | `coords` | `WorldCoords` | #### Returns `undefined` \| `LocatablePlanet` --- ### getPlanetRadii ▸ `Private` **getPlanetRadii**(`viewport`): `Map`<`PlanetLevel`, `Radii`\> One entry per planet level - radius in screen pixels of that planet level given the current viewport configuration, as well as the world radius. #### Parameters | Name | Type | | :--------- | :--------------------------------------------- | | `viewport` | [`default`](Frontend_Game_Viewport.default.md) | #### Returns `Map`<`PlanetLevel`, `Radii`\> --- ### getPlanetsAndChunks ▸ **getPlanetsAndChunks**(): `Object` #### Returns `Object` | Name | Type | | :-------------- | :--------------------------------------- | | `cachedPlanets` | `Map`<`LocationId`, `PlanetRenderInfo`\> | | `chunks` | `Set`<`Chunk`\> | --- ### getVisiblePlanetLevels ▸ `Private` **getVisiblePlanetLevels**(`viewport`): `number`[] Returns a list of planet levels which, when rendered, would result in a planet that has a size larger than one pixel. #### Parameters | Name | Type | | :--------- | :--------------------------------------------- | | `viewport` | [`default`](Frontend_Game_Viewport.default.md) | #### Returns `number`[] --- ### loadPlanetMessages ▸ `Private` **loadPlanetMessages**(): `Promise`<`void`\> #### Returns `Promise`<`void`\> --- ### recalculateViewportChunks ▸ `Private` **recalculateViewportChunks**(`viewport`): `void` #### Parameters | Name | Type | | :--------- | :--------------------------------------------- | | `viewport` | [`default`](Frontend_Game_Viewport.default.md) | #### Returns `void` --- ### recalculateViewportPlanets ▸ `Private` **recalculateViewportPlanets**(`viewport`): `void` #### Parameters | Name | Type | | :--------- | :--------------------------------------------- | | `viewport` | [`default`](Frontend_Game_Viewport.default.md) | #### Returns `void` --- ### replacePlanets ▸ `Private` **replacePlanets**(`newPlanetsInViewport`): `void` #### Parameters | Name | Type | | :--------------------- | :------------------ | | `newPlanetsInViewport` | `LocatablePlanet`[] | #### Returns `void` --- ### startRefreshing ▸ **startRefreshing**(): `void` #### Returns `void` --- ### updateLocationsAndChunks ▸ `Private` **updateLocationsAndChunks**(): `void` #### Returns `void` ================================================ FILE: docs/classes/Backend_Miner_MinerManager.HomePlanetMinerChunkStore.md ================================================ # Class: HomePlanetMinerChunkStore [Backend/Miner/MinerManager](../modules/Backend_Miner_MinerManager.md).HomePlanetMinerChunkStore ## Implements - [`ChunkStore`](../interfaces/types_darkforest_api_ChunkStoreTypes.ChunkStore.md) ## Table of contents ### Constructors - [constructor](Backend_Miner_MinerManager.HomePlanetMinerChunkStore.md#constructor) ### Properties - [initPerlinMax](Backend_Miner_MinerManager.HomePlanetMinerChunkStore.md#initperlinmax) - [initPerlinMin](Backend_Miner_MinerManager.HomePlanetMinerChunkStore.md#initperlinmin) - [minedChunkKeys](Backend_Miner_MinerManager.HomePlanetMinerChunkStore.md#minedchunkkeys) - [perlinOptions](Backend_Miner_MinerManager.HomePlanetMinerChunkStore.md#perlinoptions) ### Methods - [addChunk](Backend_Miner_MinerManager.HomePlanetMinerChunkStore.md#addchunk) - [hasMinedChunk](Backend_Miner_MinerManager.HomePlanetMinerChunkStore.md#hasminedchunk) ## Constructors ### constructor • **new HomePlanetMinerChunkStore**(`initPerlinMin`, `initPerlinMax`, `hashConfig`) #### Parameters | Name | Type | | :-------------- | :---------------------------------------------------------------- | | `initPerlinMin` | `number` | | `initPerlinMax` | `number` | | `hashConfig` | [`HashConfig`](../modules/types_global_GlobalTypes.md#hashconfig) | ## Properties ### initPerlinMax • `Private` **initPerlinMax**: `number` --- ### initPerlinMin • `Private` **initPerlinMin**: `number` --- ### minedChunkKeys • `Private` **minedChunkKeys**: `Set`<`string`\> --- ### perlinOptions • `Private` **perlinOptions**: `PerlinConfig` ## Methods ### addChunk ▸ **addChunk**(`exploredChunk`): `void` #### Parameters | Name | Type | | :-------------- | :------ | | `exploredChunk` | `Chunk` | #### Returns `void` --- ### hasMinedChunk ▸ **hasMinedChunk**(`chunkFootprint`): `boolean` #### Parameters | Name | Type | | :--------------- | :---------- | | `chunkFootprint` | `Rectangle` | #### Returns `boolean` #### Implementation of [ChunkStore](../interfaces/types_darkforest_api_ChunkStoreTypes.ChunkStore.md).[hasMinedChunk](../interfaces/types_darkforest_api_ChunkStoreTypes.ChunkStore.md#hasminedchunk) ================================================ FILE: docs/classes/Backend_Miner_MinerManager.default.md ================================================ # Class: default [Backend/Miner/MinerManager](../modules/Backend_Miner_MinerManager.md).default ## Hierarchy - `EventEmitter` ↳ **`default`** ## Table of contents ### Constructors - [constructor](Backend_Miner_MinerManager.default.md#constructor) ### Properties - [cores](Backend_Miner_MinerManager.default.md#cores) - [currentJobId](Backend_Miner_MinerManager.default.md#currentjobid) - [exploringChunk](Backend_Miner_MinerManager.default.md#exploringchunk) - [exploringChunkStart](Backend_Miner_MinerManager.default.md#exploringchunkstart) - [hashConfig](Backend_Miner_MinerManager.default.md#hashconfig) - [isExploring](Backend_Miner_MinerManager.default.md#isexploring) - [minedChunksStore](Backend_Miner_MinerManager.default.md#minedchunksstore) - [minersComplete](Backend_Miner_MinerManager.default.md#minerscomplete) - [miningPattern](Backend_Miner_MinerManager.default.md#miningpattern) - [perlinOptions](Backend_Miner_MinerManager.default.md#perlinoptions) - [planetRarity](Backend_Miner_MinerManager.default.md#planetrarity) - [useMockHash](Backend_Miner_MinerManager.default.md#usemockhash) - [workerFactory](Backend_Miner_MinerManager.default.md#workerfactory) - [workers](Backend_Miner_MinerManager.default.md#workers) - [worldRadius](Backend_Miner_MinerManager.default.md#worldradius) ### Methods - [chunkKeyToLocation](Backend_Miner_MinerManager.default.md#chunkkeytolocation) - [chunkLocationToKey](Backend_Miner_MinerManager.default.md#chunklocationtokey) - [destroy](Backend_Miner_MinerManager.default.md#destroy) - [exploreNext](Backend_Miner_MinerManager.default.md#explorenext) - [getCurrentlyExploringChunk](Backend_Miner_MinerManager.default.md#getcurrentlyexploringchunk) - [getMiningPattern](Backend_Miner_MinerManager.default.md#getminingpattern) - [initWorker](Backend_Miner_MinerManager.default.md#initworker) - [isMining](Backend_Miner_MinerManager.default.md#ismining) - [isValidExploreTarget](Backend_Miner_MinerManager.default.md#isvalidexploretarget) - [nextValidExploreTarget](Backend_Miner_MinerManager.default.md#nextvalidexploretarget) - [onDiscovered](Backend_Miner_MinerManager.default.md#ondiscovered) - [sendMessageToWorkers](Backend_Miner_MinerManager.default.md#sendmessagetoworkers) - [setCores](Backend_Miner_MinerManager.default.md#setcores) - [setMiningPattern](Backend_Miner_MinerManager.default.md#setminingpattern) - [setRadius](Backend_Miner_MinerManager.default.md#setradius) - [startExplore](Backend_Miner_MinerManager.default.md#startexplore) - [stopExplore](Backend_Miner_MinerManager.default.md#stopexplore) - [create](Backend_Miner_MinerManager.default.md#create) ## Constructors ### constructor • `Private` **new default**(`minedChunksStore`, `miningPattern`, `worldRadius`, `planetRarity`, `hashConfig`, `useMockHash`, `workerFactory`) #### Parameters | Name | Type | | :----------------- | :------------------------------------------------------------------------------- | | `minedChunksStore` | [`ChunkStore`](../interfaces/types_darkforest_api_ChunkStoreTypes.ChunkStore.md) | | `miningPattern` | [`MiningPattern`](../interfaces/Backend_Miner_MiningPatterns.MiningPattern.md) | | `worldRadius` | `number` | | `planetRarity` | `number` | | `hashConfig` | [`HashConfig`](../modules/types_global_GlobalTypes.md#hashconfig) | | `useMockHash` | `boolean` | | `workerFactory` | [`workerFactory`](../modules/Backend_Miner_MinerManager.md#workerfactory) | #### Overrides EventEmitter.constructor ## Properties ### cores • `Private` **cores**: `number` = `1` --- ### currentJobId • `Private` **currentJobId**: `number` = `0` --- ### exploringChunk • `Private` **exploringChunk**: `Object` = `{}` #### Index signature ▪ [chunkKey: `string`]: `Chunk` --- ### exploringChunkStart • `Private` **exploringChunkStart**: `Object` = `{}` #### Index signature ▪ [chunkKey: `string`]: `number` --- ### hashConfig • `Private` **hashConfig**: [`HashConfig`](../modules/types_global_GlobalTypes.md#hashconfig) --- ### isExploring • `Private` **isExploring**: `boolean` = `false` --- ### minedChunksStore • `Private` `Readonly` **minedChunksStore**: [`ChunkStore`](../interfaces/types_darkforest_api_ChunkStoreTypes.ChunkStore.md) --- ### minersComplete • `Private` **minersComplete**: `Object` = `{}` #### Index signature ▪ [chunkKey: `string`]: `number` --- ### miningPattern • `Private` **miningPattern**: [`MiningPattern`](../interfaces/Backend_Miner_MiningPatterns.MiningPattern.md) --- ### perlinOptions • `Private` **perlinOptions**: `PerlinConfig` --- ### planetRarity • `Private` `Readonly` **planetRarity**: `number` --- ### useMockHash • `Private` **useMockHash**: `boolean` --- ### workerFactory • `Private` **workerFactory**: [`workerFactory`](../modules/Backend_Miner_MinerManager.md#workerfactory) --- ### workers • `Private` **workers**: `Worker`[] --- ### worldRadius • `Private` **worldRadius**: `number` ## Methods ### chunkKeyToLocation ▸ `Private` **chunkKeyToLocation**(`chunkKey`): `undefined` \| [`Rectangle`, `number`] #### Parameters | Name | Type | | :--------- | :------- | | `chunkKey` | `string` | #### Returns `undefined` \| [`Rectangle`, `number`] --- ### chunkLocationToKey ▸ `Private` **chunkLocationToKey**(`chunkLocation`, `jobId`): `string` #### Parameters | Name | Type | | :-------------- | :---------- | | `chunkLocation` | `Rectangle` | | `jobId` | `number` | #### Returns `string` --- ### destroy ▸ **destroy**(): `void` #### Returns `void` --- ### exploreNext ▸ `Private` **exploreNext**(`fromChunk`, `jobId`): `void` #### Parameters | Name | Type | | :---------- | :---------- | | `fromChunk` | `Rectangle` | | `jobId` | `number` | #### Returns `void` --- ### getCurrentlyExploringChunk ▸ **getCurrentlyExploringChunk**(): `undefined` \| `Rectangle` #### Returns `undefined` \| `Rectangle` --- ### getMiningPattern ▸ **getMiningPattern**(): [`MiningPattern`](../interfaces/Backend_Miner_MiningPatterns.MiningPattern.md) #### Returns [`MiningPattern`](../interfaces/Backend_Miner_MiningPatterns.MiningPattern.md) --- ### initWorker ▸ `Private` **initWorker**(`index`): `void` #### Parameters | Name | Type | | :------ | :------- | | `index` | `number` | #### Returns `void` --- ### isMining ▸ **isMining**(): `boolean` #### Returns `boolean` --- ### isValidExploreTarget ▸ `Private` **isValidExploreTarget**(`chunkLocation`): `boolean` #### Parameters | Name | Type | | :-------------- | :---------- | | `chunkLocation` | `Rectangle` | #### Returns `boolean` --- ### nextValidExploreTarget ▸ `Private` **nextValidExploreTarget**(`chunkLocation`, `jobId`): `Promise`<`undefined` \| `Rectangle`\> #### Parameters | Name | Type | | :-------------- | :---------- | | `chunkLocation` | `Rectangle` | | `jobId` | `number` | #### Returns `Promise`<`undefined` \| `Rectangle`\> --- ### onDiscovered ▸ `Private` **onDiscovered**(`exploredChunk`, `jobId`): `Promise`<`void`\> #### Parameters | Name | Type | | :-------------- | :------- | | `exploredChunk` | `Chunk` | | `jobId` | `number` | #### Returns `Promise`<`void`\> --- ### sendMessageToWorkers ▸ `Private` **sendMessageToWorkers**(`chunkToExplore`, `jobId`): `void` #### Parameters | Name | Type | | :--------------- | :---------- | | `chunkToExplore` | `Rectangle` | | `jobId` | `number` | #### Returns `void` --- ### setCores ▸ **setCores**(`nCores`): `void` #### Parameters | Name | Type | | :------- | :------- | | `nCores` | `number` | #### Returns `void` --- ### setMiningPattern ▸ **setMiningPattern**(`pattern`): `void` #### Parameters | Name | Type | | :-------- | :----------------------------------------------------------------------------- | | `pattern` | [`MiningPattern`](../interfaces/Backend_Miner_MiningPatterns.MiningPattern.md) | #### Returns `void` --- ### setRadius ▸ **setRadius**(`radius`): `void` #### Parameters | Name | Type | | :------- | :------- | | `radius` | `number` | #### Returns `void` --- ### startExplore ▸ **startExplore**(): `void` #### Returns `void` --- ### stopExplore ▸ **stopExplore**(): `void` #### Returns `void` --- ### create ▸ `Static` **create**(`chunkStore`, `miningPattern`, `worldRadius`, `planetRarity`, `hashConfig`, `useMockHash?`, `workerFactory?`): [`default`](Backend_Miner_MinerManager.default.md) #### Parameters | Name | Type | Default value | | :-------------- | :------------------------------------------------------------------------------- | :-------------- | | `chunkStore` | [`ChunkStore`](../interfaces/types_darkforest_api_ChunkStoreTypes.ChunkStore.md) | `undefined` | | `miningPattern` | [`MiningPattern`](../interfaces/Backend_Miner_MiningPatterns.MiningPattern.md) | `undefined` | | `worldRadius` | `number` | `undefined` | | `planetRarity` | `number` | `undefined` | | `hashConfig` | [`HashConfig`](../modules/types_global_GlobalTypes.md#hashconfig) | `undefined` | | `useMockHash` | `boolean` | `false` | | `workerFactory` | [`workerFactory`](../modules/Backend_Miner_MinerManager.md#workerfactory) | `defaultWorker` | #### Returns [`default`](Backend_Miner_MinerManager.default.md) ================================================ FILE: docs/classes/Backend_Miner_MiningPatterns.SpiralPattern.md ================================================ # Class: SpiralPattern [Backend/Miner/MiningPatterns](../modules/Backend_Miner_MiningPatterns.md).SpiralPattern ## Implements - [`MiningPattern`](../interfaces/Backend_Miner_MiningPatterns.MiningPattern.md) ## Table of contents ### Constructors - [constructor](Backend_Miner_MiningPatterns.SpiralPattern.md#constructor) ### Properties - [chunkSideLength](Backend_Miner_MiningPatterns.SpiralPattern.md#chunksidelength) - [fromChunk](Backend_Miner_MiningPatterns.SpiralPattern.md#fromchunk) - [type](Backend_Miner_MiningPatterns.SpiralPattern.md#type) ### Methods - [nextChunk](Backend_Miner_MiningPatterns.SpiralPattern.md#nextchunk) ## Constructors ### constructor • **new SpiralPattern**(`center`, `chunkSize`) #### Parameters | Name | Type | | :---------- | :------------ | | `center` | `WorldCoords` | | `chunkSize` | `number` | ## Properties ### chunkSideLength • **chunkSideLength**: `number` --- ### fromChunk • **fromChunk**: `Rectangle` #### Implementation of [MiningPattern](../interfaces/Backend_Miner_MiningPatterns.MiningPattern.md).[fromChunk](../interfaces/Backend_Miner_MiningPatterns.MiningPattern.md#fromchunk) --- ### type • **type**: [`MiningPatternType`](../enums/Backend_Miner_MiningPatterns.MiningPatternType.md) = `MiningPatternType.Spiral` #### Implementation of [MiningPattern](../interfaces/Backend_Miner_MiningPatterns.MiningPattern.md).[type](../interfaces/Backend_Miner_MiningPatterns.MiningPattern.md#type) ## Methods ### nextChunk ▸ **nextChunk**(`chunk`): `Rectangle` #### Parameters | Name | Type | | :------ | :---------- | | `chunk` | `Rectangle` | #### Returns `Rectangle` #### Implementation of [MiningPattern](../interfaces/Backend_Miner_MiningPatterns.MiningPattern.md).[nextChunk](../interfaces/Backend_Miner_MiningPatterns.MiningPattern.md#nextchunk) ================================================ FILE: docs/classes/Backend_Miner_MiningPatterns.SwissCheesePattern.md ================================================ # Class: SwissCheesePattern [Backend/Miner/MiningPatterns](../modules/Backend_Miner_MiningPatterns.md).SwissCheesePattern ## Implements - [`MiningPattern`](../interfaces/Backend_Miner_MiningPatterns.MiningPattern.md) ## Table of contents ### Constructors - [constructor](Backend_Miner_MiningPatterns.SwissCheesePattern.md#constructor) ### Properties - [chunkSideLength](Backend_Miner_MiningPatterns.SwissCheesePattern.md#chunksidelength) - [fromChunk](Backend_Miner_MiningPatterns.SwissCheesePattern.md#fromchunk) - [type](Backend_Miner_MiningPatterns.SwissCheesePattern.md#type) ### Methods - [nextChunk](Backend_Miner_MiningPatterns.SwissCheesePattern.md#nextchunk) ## Constructors ### constructor • **new SwissCheesePattern**(`center`, `chunkSize`) #### Parameters | Name | Type | | :---------- | :------------ | | `center` | `WorldCoords` | | `chunkSize` | `number` | ## Properties ### chunkSideLength • **chunkSideLength**: `number` --- ### fromChunk • **fromChunk**: `Rectangle` #### Implementation of [MiningPattern](../interfaces/Backend_Miner_MiningPatterns.MiningPattern.md).[fromChunk](../interfaces/Backend_Miner_MiningPatterns.MiningPattern.md#fromchunk) --- ### type • **type**: [`MiningPatternType`](../enums/Backend_Miner_MiningPatterns.MiningPatternType.md) = `MiningPatternType.SwissCheese` #### Implementation of [MiningPattern](../interfaces/Backend_Miner_MiningPatterns.MiningPattern.md).[type](../interfaces/Backend_Miner_MiningPatterns.MiningPattern.md#type) ## Methods ### nextChunk ▸ **nextChunk**(`chunk`): `Rectangle` #### Parameters | Name | Type | | :------ | :---------- | | `chunk` | `Rectangle` | #### Returns `Rectangle` #### Implementation of [MiningPattern](../interfaces/Backend_Miner_MiningPatterns.MiningPattern.md).[nextChunk](../interfaces/Backend_Miner_MiningPatterns.MiningPattern.md#nextchunk) ================================================ FILE: docs/classes/Backend_Miner_MiningPatterns.TowardsCenterPattern.md ================================================ # Class: TowardsCenterPattern [Backend/Miner/MiningPatterns](../modules/Backend_Miner_MiningPatterns.md).TowardsCenterPattern ## Implements - [`MiningPattern`](../interfaces/Backend_Miner_MiningPatterns.MiningPattern.md) ## Table of contents ### Constructors - [constructor](Backend_Miner_MiningPatterns.TowardsCenterPattern.md#constructor) ### Properties - [chunkSideLength](Backend_Miner_MiningPatterns.TowardsCenterPattern.md#chunksidelength) - [fromChunk](Backend_Miner_MiningPatterns.TowardsCenterPattern.md#fromchunk) - [maxWidth](Backend_Miner_MiningPatterns.TowardsCenterPattern.md#maxwidth) - [tipX](Backend_Miner_MiningPatterns.TowardsCenterPattern.md#tipx) - [tipY](Backend_Miner_MiningPatterns.TowardsCenterPattern.md#tipy) - [type](Backend_Miner_MiningPatterns.TowardsCenterPattern.md#type) ### Methods - [nextChunk](Backend_Miner_MiningPatterns.TowardsCenterPattern.md#nextchunk) ## Constructors ### constructor • **new TowardsCenterPattern**(`center`, `chunkSize`) #### Parameters | Name | Type | | :---------- | :------------ | | `center` | `WorldCoords` | | `chunkSize` | `number` | ## Properties ### chunkSideLength • **chunkSideLength**: `number` --- ### fromChunk • **fromChunk**: `Rectangle` #### Implementation of [MiningPattern](../interfaces/Backend_Miner_MiningPatterns.MiningPattern.md).[fromChunk](../interfaces/Backend_Miner_MiningPatterns.MiningPattern.md#fromchunk) --- ### maxWidth • `Private` **maxWidth**: `number` = `1600` --- ### tipX • `Private` **tipX**: `number` --- ### tipY • `Private` **tipY**: `number` --- ### type • **type**: [`MiningPatternType`](../enums/Backend_Miner_MiningPatterns.MiningPatternType.md) = `MiningPatternType.TowardsCenter` #### Implementation of [MiningPattern](../interfaces/Backend_Miner_MiningPatterns.MiningPattern.md).[type](../interfaces/Backend_Miner_MiningPatterns.MiningPattern.md#type) ## Methods ### nextChunk ▸ **nextChunk**(`chunk`): `Rectangle` #### Parameters | Name | Type | | :------ | :---------- | | `chunk` | `Rectangle` | #### Returns `Rectangle` #### Implementation of [MiningPattern](../interfaces/Backend_Miner_MiningPatterns.MiningPattern.md).[nextChunk](../interfaces/Backend_Miner_MiningPatterns.MiningPattern.md#nextchunk) ================================================ FILE: docs/classes/Backend_Miner_MiningPatterns.TowardsCenterPatternV2.md ================================================ # Class: TowardsCenterPatternV2 [Backend/Miner/MiningPatterns](../modules/Backend_Miner_MiningPatterns.md).TowardsCenterPatternV2 ## Implements - [`MiningPattern`](../interfaces/Backend_Miner_MiningPatterns.MiningPattern.md) ## Table of contents ### Constructors - [constructor](Backend_Miner_MiningPatterns.TowardsCenterPatternV2.md#constructor) ### Properties - [chunkSideLength](Backend_Miner_MiningPatterns.TowardsCenterPatternV2.md#chunksidelength) - [fromChunk](Backend_Miner_MiningPatterns.TowardsCenterPatternV2.md#fromchunk) - [rowRadius](Backend_Miner_MiningPatterns.TowardsCenterPatternV2.md#rowradius) - [slopeToCenter](Backend_Miner_MiningPatterns.TowardsCenterPatternV2.md#slopetocenter) - [type](Backend_Miner_MiningPatterns.TowardsCenterPatternV2.md#type) - [yDominant](Backend_Miner_MiningPatterns.TowardsCenterPatternV2.md#ydominant) ### Methods - [nextChunk](Backend_Miner_MiningPatterns.TowardsCenterPatternV2.md#nextchunk) - [toChunk](Backend_Miner_MiningPatterns.TowardsCenterPatternV2.md#tochunk) ## Constructors ### constructor • **new TowardsCenterPatternV2**(`center`, `chunkSize`) #### Parameters | Name | Type | | :---------- | :------------ | | `center` | `WorldCoords` | | `chunkSize` | `number` | ## Properties ### chunkSideLength • **chunkSideLength**: `number` --- ### fromChunk • **fromChunk**: `Rectangle` #### Implementation of [MiningPattern](../interfaces/Backend_Miner_MiningPatterns.MiningPattern.md).[fromChunk](../interfaces/Backend_Miner_MiningPatterns.MiningPattern.md#fromchunk) --- ### rowRadius • `Private` **rowRadius**: `number` --- ### slopeToCenter • `Private` **slopeToCenter**: `number` --- ### type • **type**: [`MiningPatternType`](../enums/Backend_Miner_MiningPatterns.MiningPatternType.md) = `MiningPatternType.TowardsCenterV2` #### Implementation of [MiningPattern](../interfaces/Backend_Miner_MiningPatterns.MiningPattern.md).[type](../interfaces/Backend_Miner_MiningPatterns.MiningPattern.md#type) --- ### yDominant • `Private` **yDominant**: `boolean` ## Methods ### nextChunk ▸ **nextChunk**(`chunk`): `Rectangle` #### Parameters | Name | Type | | :------ | :---------- | | `chunk` | `Rectangle` | #### Returns `Rectangle` #### Implementation of [MiningPattern](../interfaces/Backend_Miner_MiningPatterns.MiningPattern.md).[nextChunk](../interfaces/Backend_Miner_MiningPatterns.MiningPattern.md#nextchunk) --- ### toChunk ▸ **toChunk**(`coord`): `number` #### Parameters | Name | Type | | :------ | :------- | | `coord` | `number` | #### Returns `number` ================================================ FILE: docs/classes/Backend_Network_EventLogger.EventLogger.md ================================================ # Class: EventLogger [Backend/Network/EventLogger](../modules/Backend_Network_EventLogger.md).EventLogger ## Table of contents ### Constructors - [constructor](Backend_Network_EventLogger.EventLogger.md#constructor) ### Methods - [logEvent](Backend_Network_EventLogger.EventLogger.md#logevent) - [augmentEvent](Backend_Network_EventLogger.EventLogger.md#augmentevent) ## Constructors ### constructor • **new EventLogger**() ## Methods ### logEvent ▸ **logEvent**(`eventType`, `event`): `void` #### Parameters | Name | Type | | :---------- | :--------------------------------------------------------------- | | `eventType` | [`EventType`](../enums/Backend_Network_EventLogger.EventType.md) | | `event` | `unknown` | #### Returns `void` --- ### augmentEvent ▸ `Static` `Private` **augmentEvent**(`event`, `eventType`): `Object` #### Parameters | Name | Type | | :---------- | :--------------------------------------------------------------- | | `event` | `unknown` | | `eventType` | [`EventType`](../enums/Backend_Network_EventLogger.EventType.md) | #### Returns `Object` | Name | Type | | :-------------- | :--------------------------------------------------------------- | | `df_event_type` | [`EventType`](../enums/Backend_Network_EventLogger.EventType.md) | ================================================ FILE: docs/classes/Backend_Storage_PersistentChunkStore.default.md ================================================ # Class: default [Backend/Storage/PersistentChunkStore](../modules/Backend_Storage_PersistentChunkStore.md).default ## Implements - [`ChunkStore`](../interfaces/types_darkforest_api_ChunkStoreTypes.ChunkStore.md) ## Table of contents ### Constructors - [constructor](Backend_Storage_PersistentChunkStore.default.md#constructor) ### Properties - [account](Backend_Storage_PersistentChunkStore.default.md#account) - [chunkMap](Backend_Storage_PersistentChunkStore.default.md#chunkmap) - [confirmedTxHashes](Backend_Storage_PersistentChunkStore.default.md#confirmedtxhashes) - [contractAddress](Backend_Storage_PersistentChunkStore.default.md#contractaddress) - [db](Backend_Storage_PersistentChunkStore.default.md#db) - [diagnosticUpdater](Backend_Storage_PersistentChunkStore.default.md#diagnosticupdater) - [nUpdatesLastTwoMins](Backend_Storage_PersistentChunkStore.default.md#nupdateslasttwomins) - [queuedChunkWrites](Backend_Storage_PersistentChunkStore.default.md#queuedchunkwrites) - [throttledSaveChunkCacheToDisk](Backend_Storage_PersistentChunkStore.default.md#throttledsavechunkcachetodisk) ### Methods - [addChunk](Backend_Storage_PersistentChunkStore.default.md#addchunk) - [addHomeLocation](Backend_Storage_PersistentChunkStore.default.md#addhomelocation) - [allChunks](Backend_Storage_PersistentChunkStore.default.md#allchunks) - [bulkSetKeyInCollection](Backend_Storage_PersistentChunkStore.default.md#bulksetkeyincollection) - [confirmHomeLocation](Backend_Storage_PersistentChunkStore.default.md#confirmhomelocation) - [destroy](Backend_Storage_PersistentChunkStore.default.md#destroy) - [getChunkByFootprint](Backend_Storage_PersistentChunkStore.default.md#getchunkbyfootprint) - [getChunkById](Backend_Storage_PersistentChunkStore.default.md#getchunkbyid) - [getHomeLocations](Backend_Storage_PersistentChunkStore.default.md#gethomelocations) - [getKey](Backend_Storage_PersistentChunkStore.default.md#getkey) - [getMinedSubChunks](Backend_Storage_PersistentChunkStore.default.md#getminedsubchunks) - [getSavedClaimedCoords](Backend_Storage_PersistentChunkStore.default.md#getsavedclaimedcoords) - [getSavedRevealedCoords](Backend_Storage_PersistentChunkStore.default.md#getsavedrevealedcoords) - [getSavedTouchedPlanetIds](Backend_Storage_PersistentChunkStore.default.md#getsavedtouchedplanetids) - [getUnconfirmedSubmittedEthTxs](Backend_Storage_PersistentChunkStore.default.md#getunconfirmedsubmittedethtxs) - [hasMinedChunk](Backend_Storage_PersistentChunkStore.default.md#hasminedchunk) - [loadChunks](Backend_Storage_PersistentChunkStore.default.md#loadchunks) - [loadModalPositions](Backend_Storage_PersistentChunkStore.default.md#loadmodalpositions) - [loadPlugins](Backend_Storage_PersistentChunkStore.default.md#loadplugins) - [onEthTxComplete](Backend_Storage_PersistentChunkStore.default.md#onethtxcomplete) - [onEthTxSubmit](Backend_Storage_PersistentChunkStore.default.md#onethtxsubmit) - [persistQueuedChunks](Backend_Storage_PersistentChunkStore.default.md#persistqueuedchunks) - [recomputeSaveThrottleAfterUpdate](Backend_Storage_PersistentChunkStore.default.md#recomputesavethrottleafterupdate) - [removeKey](Backend_Storage_PersistentChunkStore.default.md#removekey) - [saveClaimedCoords](Backend_Storage_PersistentChunkStore.default.md#saveclaimedcoords) - [saveModalPositions](Backend_Storage_PersistentChunkStore.default.md#savemodalpositions) - [savePlugins](Backend_Storage_PersistentChunkStore.default.md#saveplugins) - [saveRevealedCoords](Backend_Storage_PersistentChunkStore.default.md#saverevealedcoords) - [saveTouchedPlanetIds](Backend_Storage_PersistentChunkStore.default.md#savetouchedplanetids) - [setDiagnosticUpdater](Backend_Storage_PersistentChunkStore.default.md#setdiagnosticupdater) - [setKey](Backend_Storage_PersistentChunkStore.default.md#setkey) - [create](Backend_Storage_PersistentChunkStore.default.md#create) ## Constructors ### constructor • **new default**(`__namedParameters`) #### Parameters | Name | Type | | :------------------ | :--------------------------- | | `__namedParameters` | `PersistentChunkStoreConfig` | ## Properties ### account • `Private` **account**: `EthAddress` --- ### chunkMap • `Private` **chunkMap**: `Map`<[`ChunkId`](../modules/types_darkforest_api_ChunkStoreTypes.md#chunkid), `Chunk`\> --- ### confirmedTxHashes • `Private` **confirmedTxHashes**: `Set`<`string`\> --- ### contractAddress • `Private` **contractAddress**: `EthAddress` --- ### db • `Private` **db**: `IDBPDatabase`<`unknown`\> --- ### diagnosticUpdater • `Private` `Optional` **diagnosticUpdater**: `DiagnosticUpdater` --- ### nUpdatesLastTwoMins • `Private` **nUpdatesLastTwoMins**: `number` = `0` --- ### queuedChunkWrites • `Private` **queuedChunkWrites**: `DBTx`[] --- ### throttledSaveChunkCacheToDisk • `Private` **throttledSaveChunkCacheToDisk**: `DebouncedFunc`<() => `Promise`<`void`\>\> ## Methods ### addChunk ▸ **addChunk**(`chunk`, `persistChunk?`): `void` When a chunk is mined, or a chunk is imported via map import, or a chunk is loaded from persistent storage for the first time, we need to add this chunk to the game. This function allows you to add a new chunk to the game, and optionally persist that chunk. The reason you might not want to persist the chunk is if you are sure that you got it from persistent storage. i.e. it already exists in persistent storage. #### Parameters | Name | Type | Default value | | :------------- | :-------- | :------------ | | `chunk` | `Chunk` | `undefined` | | `persistChunk` | `boolean` | `true` | #### Returns `void` --- ### addHomeLocation ▸ **addHomeLocation**(`location`): `Promise`<`void`\> #### Parameters | Name | Type | | :--------- | :-------------- | | `location` | `WorldLocation` | #### Returns `Promise`<`void`\> --- ### allChunks ▸ **allChunks**(): `Iterable`<`Chunk`\> #### Returns `Iterable`<`Chunk`\> --- ### bulkSetKeyInCollection ▸ `Private` **bulkSetKeyInCollection**(`updateChunkTxs`, `collection`): `Promise`<`void`\> #### Parameters | Name | Type | | :--------------- | :------------ | | `updateChunkTxs` | `DBTx`[] | | `collection` | `ObjectStore` | #### Returns `Promise`<`void`\> --- ### confirmHomeLocation ▸ **confirmHomeLocation**(`location`): `Promise`<`void`\> #### Parameters | Name | Type | | :--------- | :-------------- | | `location` | `WorldLocation` | #### Returns `Promise`<`void`\> --- ### destroy ▸ **destroy**(): `void` #### Returns `void` --- ### getChunkByFootprint ▸ **getChunkByFootprint**(`chunkLoc`): `undefined` \| `Chunk` Returns the explored chunk data for the given rectangle if that chunk has been mined. If this chunk is entirely contained within another bigger chunk that has been mined, return that chunk. `chunkLoc` is an aligned square, as defined in ChunkUtils.ts in the `getSiblingLocations` function. #### Parameters | Name | Type | | :--------- | :---------- | | `chunkLoc` | `Rectangle` | #### Returns `undefined` \| `Chunk` --- ### getChunkById ▸ `Private` **getChunkById**(`chunkId`): `undefined` \| `Chunk` #### Parameters | Name | Type | | :-------- | :---------------------------------------------------------------------- | | `chunkId` | [`ChunkId`](../modules/types_darkforest_api_ChunkStoreTypes.md#chunkid) | #### Returns `undefined` \| `Chunk` --- ### getHomeLocations ▸ **getHomeLocations**(): `Promise`<`WorldLocation`[]\> we keep a list rather than a single location, since client/contract can often go out of sync on initialization - if client thinks that init failed but is wrong, it will prompt user to initialize with new home coords, which bricks the user's account. #### Returns `Promise`<`WorldLocation`[]\> --- ### getKey ▸ `Private` **getKey**(`key`, `objStore?`): `Promise`<`undefined` \| `string`\> Important! This sets the key in indexed db per account and per contract. This means the same client can connect to multiple different dark forest contracts, with multiple different accounts, and the persistent storage will not overwrite data that is not relevant for the current configuration of the client. #### Parameters | Name | Type | Default value | | :--------- | :------------ | :-------------------- | | `key` | `string` | `undefined` | | `objStore` | `ObjectStore` | `ObjectStore.DEFAULT` | #### Returns `Promise`<`undefined` \| `string`\> --- ### getMinedSubChunks ▸ `Private` **getMinedSubChunks**(`chunk`): `Chunk`[] Returns all the mined chunks with smaller sidelength strictly contained in the chunk. TODO: move this into ChunkUtils, and also make use of it, the way that it is currently used, in the function named `addToChunkMap`. #### Parameters | Name | Type | | :------ | :------ | | `chunk` | `Chunk` | #### Returns `Chunk`[] --- ### getSavedClaimedCoords ▸ **getSavedClaimedCoords**(): `Promise`<`ClaimedCoords`[]\> #### Returns `Promise`<`ClaimedCoords`[]\> --- ### getSavedRevealedCoords ▸ **getSavedRevealedCoords**(): `Promise`<`RevealedCoords`[]\> #### Returns `Promise`<`RevealedCoords`[]\> --- ### getSavedTouchedPlanetIds ▸ **getSavedTouchedPlanetIds**(): `Promise`<`LocationId`[]\> #### Returns `Promise`<`LocationId`[]\> --- ### getUnconfirmedSubmittedEthTxs ▸ **getUnconfirmedSubmittedEthTxs**(): `Promise`<`PersistedTransaction`<`TxIntent`\>[]\> #### Returns `Promise`<`PersistedTransaction`<`TxIntent`\>[]\> --- ### hasMinedChunk ▸ **hasMinedChunk**(`chunkLoc`): `boolean` #### Parameters | Name | Type | | :--------- | :---------- | | `chunkLoc` | `Rectangle` | #### Returns `boolean` #### Implementation of [ChunkStore](../interfaces/types_darkforest_api_ChunkStoreTypes.ChunkStore.md).[hasMinedChunk](../interfaces/types_darkforest_api_ChunkStoreTypes.ChunkStore.md#hasminedchunk) --- ### loadChunks ▸ `Private` **loadChunks**(): `Promise`<`void`\> This function loads all chunks persisted in the user's storage into the game. #### Returns `Promise`<`void`\> --- ### loadModalPositions ▸ **loadModalPositions**(): `Promise`<`Map`<`ModalId`, `ModalPosition`\>\> #### Returns `Promise`<`Map`<`ModalId`, `ModalPosition`\>\> --- ### loadPlugins ▸ **loadPlugins**(): `Promise`<[`SerializedPlugin`](../interfaces/Backend_Plugins_SerializedPlugin.SerializedPlugin.md)[]\> #### Returns `Promise`<[`SerializedPlugin`](../interfaces/Backend_Plugins_SerializedPlugin.SerializedPlugin.md)[]\> --- ### onEthTxComplete ▸ **onEthTxComplete**(`txHash`): `Promise`<`void`\> Partner function to {@link PersistentChunkStore#onEthTxSubmit} #### Parameters | Name | Type | | :------- | :------- | | `txHash` | `string` | #### Returns `Promise`<`void`\> --- ### onEthTxSubmit ▸ **onEthTxSubmit**(`tx`): `Promise`<`void`\> Whenever a transaction is submitted, it is persisted. When the transaction either fails or succeeds, it is un-persisted. The reason we persist submitted transactions is to be able to wait for them upon a fresh start of the game if you close the game before a transaction confirms. #### Parameters | Name | Type | | :--- | :------------------------- | | `tx` | `Transaction`<`TxIntent`\> | #### Returns `Promise`<`void`\> --- ### persistQueuedChunks ▸ `Private` **persistQueuedChunks**(): `Promise`<`void`\> Rather than saving a chunk immediately after it's mined, we queue up new chunks, and periodically save them. This function gets all of the queued new chunks, and persists them to indexed db. #### Returns `Promise`<`void`\> --- ### recomputeSaveThrottleAfterUpdate ▸ `Private` **recomputeSaveThrottleAfterUpdate**(): `void` #### Returns `void` --- ### removeKey ▸ `Private` **removeKey**(`key`, `objStore?`): `Promise`<`void`\> #### Parameters | Name | Type | Default value | | :--------- | :------------ | :-------------------- | | `key` | `string` | `undefined` | | `objStore` | `ObjectStore` | `ObjectStore.DEFAULT` | #### Returns `Promise`<`void`\> --- ### saveClaimedCoords ▸ **saveClaimedCoords**(`claimedCoordTupps`): `Promise`<`void`\> #### Parameters | Name | Type | | :------------------ | :---------------- | | `claimedCoordTupps` | `ClaimedCoords`[] | #### Returns `Promise`<`void`\> --- ### saveModalPositions ▸ **saveModalPositions**(`modalPositions`): `Promise`<`void`\> #### Parameters | Name | Type | | :--------------- | :--------------------------------- | | `modalPositions` | `Map`<`ModalId`, `ModalPosition`\> | #### Returns `Promise`<`void`\> --- ### savePlugins ▸ **savePlugins**(`plugins`): `Promise`<`void`\> #### Parameters | Name | Type | | :-------- | :----------------------------------------------------------------------------------------- | | `plugins` | [`SerializedPlugin`](../interfaces/Backend_Plugins_SerializedPlugin.SerializedPlugin.md)[] | #### Returns `Promise`<`void`\> --- ### saveRevealedCoords ▸ **saveRevealedCoords**(`revealedCoordTups`): `Promise`<`void`\> #### Parameters | Name | Type | | :------------------ | :----------------- | | `revealedCoordTups` | `RevealedCoords`[] | #### Returns `Promise`<`void`\> --- ### saveTouchedPlanetIds ▸ **saveTouchedPlanetIds**(`ids`): `Promise`<`void`\> #### Parameters | Name | Type | | :---- | :------------- | | `ids` | `LocationId`[] | #### Returns `Promise`<`void`\> --- ### setDiagnosticUpdater ▸ **setDiagnosticUpdater**(`diagnosticUpdater?`): `void` #### Parameters | Name | Type | | :------------------- | :------------------ | | `diagnosticUpdater?` | `DiagnosticUpdater` | #### Returns `void` --- ### setKey ▸ `Private` **setKey**(`key`, `value`, `objStore?`): `Promise`<`void`\> Important! This sets the key in indexed db per account and per contract. This means the same client can connect to multiple different dark forest contracts, with multiple different accounts, and the persistent storage will not overwrite data that is not relevant for the current configuration of the client. #### Parameters | Name | Type | Default value | | :--------- | :------------ | :-------------------- | | `key` | `string` | `undefined` | | `value` | `string` | `undefined` | | `objStore` | `ObjectStore` | `ObjectStore.DEFAULT` | #### Returns `Promise`<`void`\> --- ### create ▸ `Static` **create**(`__namedParameters`): `Promise`<[`default`](Backend_Storage_PersistentChunkStore.default.md)\> NOTE! if you're creating a new object store, it will not be _added_ to existing dark forest accounts. This creation code runs once per account. Therefore, if you're adding a new object store, and need to test it out, you must either clear the indexed db databse for this account, or create a brand new account. #### Parameters | Name | Type | | :------------------ | :-------------------------------------------- | | `__namedParameters` | `Omit`<`PersistentChunkStoreConfig`, `"db"`\> | #### Returns `Promise`<[`default`](Backend_Storage_PersistentChunkStore.default.md)\> ================================================ FILE: docs/classes/Backend_Storage_ReaderDataStore.default.md ================================================ # Class: default [Backend/Storage/ReaderDataStore](../modules/Backend_Storage_ReaderDataStore.md).default A data store that allows you to retrieve data from the contract, and combine it with data that is stored in this browser about a particular user. ## Table of contents ### Constructors - [constructor](Backend_Storage_ReaderDataStore.default.md#constructor) ### Properties - [addressTwitterMap](Backend_Storage_ReaderDataStore.default.md#addresstwittermap) - [contractConstants](Backend_Storage_ReaderDataStore.default.md#contractconstants) - [contractsAPI](Backend_Storage_ReaderDataStore.default.md#contractsapi) - [persistentChunkStore](Backend_Storage_ReaderDataStore.default.md#persistentchunkstore) - [viewer](Backend_Storage_ReaderDataStore.default.md#viewer) ### Methods - [destroy](Backend_Storage_ReaderDataStore.default.md#destroy) - [getBiome](Backend_Storage_ReaderDataStore.default.md#getbiome) - [getTwitter](Backend_Storage_ReaderDataStore.default.md#gettwitter) - [getViewer](Backend_Storage_ReaderDataStore.default.md#getviewer) - [loadArtifactFromContract](Backend_Storage_ReaderDataStore.default.md#loadartifactfromcontract) - [loadPlanetFromContract](Backend_Storage_ReaderDataStore.default.md#loadplanetfromcontract) - [setPlanetLocationIfKnown](Backend_Storage_ReaderDataStore.default.md#setplanetlocationifknown) - [spaceTypeFromPerlin](Backend_Storage_ReaderDataStore.default.md#spacetypefromperlin) - [create](Backend_Storage_ReaderDataStore.default.md#create) ## Constructors ### constructor • `Private` **new default**(`__namedParameters`) #### Parameters | Name | Type | | :------------------ | :---------------------- | | `__namedParameters` | `ReaderDataStoreConfig` | ## Properties ### addressTwitterMap • `Private` `Readonly` **addressTwitterMap**: [`AddressTwitterMap`](../modules/types_darkforest_api_UtilityServerAPITypes.md#addresstwittermap) --- ### contractConstants • `Private` `Readonly` **contractConstants**: [`ContractConstants`](../interfaces/types_darkforest_api_ContractsAPITypes.ContractConstants.md) --- ### contractsAPI • `Private` `Readonly` **contractsAPI**: [`ContractsAPI`](Backend_GameLogic_ContractsAPI.ContractsAPI.md) --- ### persistentChunkStore • `Private` `Readonly` **persistentChunkStore**: `undefined` \| [`default`](Backend_Storage_PersistentChunkStore.default.md) --- ### viewer • `Private` `Readonly` **viewer**: `undefined` \| `EthAddress` ## Methods ### destroy ▸ **destroy**(): `void` #### Returns `void` --- ### getBiome ▸ `Private` **getBiome**(`loc`): `Biome` #### Parameters | Name | Type | | :---- | :-------------- | | `loc` | `WorldLocation` | #### Returns `Biome` --- ### getTwitter ▸ **getTwitter**(`owner`): `undefined` \| `string` #### Parameters | Name | Type | | :------ | :-------------------------- | | `owner` | `undefined` \| `EthAddress` | #### Returns `undefined` \| `string` --- ### getViewer ▸ **getViewer**(): `undefined` \| `EthAddress` #### Returns `undefined` \| `EthAddress` --- ### loadArtifactFromContract ▸ **loadArtifactFromContract**(`artifactId`): `Promise`<`Artifact`\> #### Parameters | Name | Type | | :----------- | :----------- | | `artifactId` | `ArtifactId` | #### Returns `Promise`<`Artifact`\> --- ### loadPlanetFromContract ▸ **loadPlanetFromContract**(`planetId`): `Promise`<`Planet` \| `LocatablePlanet`\> #### Parameters | Name | Type | | :--------- | :----------- | | `planetId` | `LocationId` | #### Returns `Promise`<`Planet` \| `LocatablePlanet`\> --- ### setPlanetLocationIfKnown ▸ `Private` **setPlanetLocationIfKnown**(`planet`): `void` #### Parameters | Name | Type | | :------- | :------- | | `planet` | `Planet` | #### Returns `void` --- ### spaceTypeFromPerlin ▸ `Private` **spaceTypeFromPerlin**(`perlin`): `SpaceType` #### Parameters | Name | Type | | :------- | :------- | | `perlin` | `number` | #### Returns `SpaceType` --- ### create ▸ `Static` **create**(`__namedParameters`): `Promise`<[`default`](Backend_Storage_ReaderDataStore.default.md)\> #### Parameters | Name | Type | | :---------------------------------- | :-------------------------- | | `__namedParameters` | `Object` | | `__namedParameters.connection` | `EthConnection` | | `__namedParameters.contractAddress` | `EthAddress` | | `__namedParameters.viewer` | `undefined` \| `EthAddress` | #### Returns `Promise`<[`default`](Backend_Storage_ReaderDataStore.default.md)\> ================================================ FILE: docs/classes/Backend_Utils_SnarkArgsHelper.default.md ================================================ # Class: default [Backend/Utils/SnarkArgsHelper](../modules/Backend_Utils_SnarkArgsHelper.md).default ## Table of contents ### Constructors - [constructor](Backend_Utils_SnarkArgsHelper.default.md#constructor) ### Properties - [biomebasePerlinOpts](Backend_Utils_SnarkArgsHelper.default.md#biomebaseperlinopts) - [hashConfig](Backend_Utils_SnarkArgsHelper.default.md#hashconfig) - [moveSnarkCache](Backend_Utils_SnarkArgsHelper.default.md#movesnarkcache) - [planetHashMimc](Backend_Utils_SnarkArgsHelper.default.md#planethashmimc) - [snarkProverQueue](Backend_Utils_SnarkArgsHelper.default.md#snarkproverqueue) - [spaceTypePerlinOpts](Backend_Utils_SnarkArgsHelper.default.md#spacetypeperlinopts) - [terminal](Backend_Utils_SnarkArgsHelper.default.md#terminal) - [useMockHash](Backend_Utils_SnarkArgsHelper.default.md#usemockhash) - [DEFAULT_SNARK_CACHE_SIZE](Backend_Utils_SnarkArgsHelper.default.md#default_snark_cache_size) ### Methods - [fakeBiomebaseProof](Backend_Utils_SnarkArgsHelper.default.md#fakebiomebaseproof) - [fakeInitProof](Backend_Utils_SnarkArgsHelper.default.md#fakeinitproof) - [fakeMoveProof](Backend_Utils_SnarkArgsHelper.default.md#fakemoveproof) - [fakeRevealProof](Backend_Utils_SnarkArgsHelper.default.md#fakerevealproof) - [getFindArtifactArgs](Backend_Utils_SnarkArgsHelper.default.md#getfindartifactargs) - [getInitArgs](Backend_Utils_SnarkArgsHelper.default.md#getinitargs) - [getMoveArgs](Backend_Utils_SnarkArgsHelper.default.md#getmoveargs) - [getRevealArgs](Backend_Utils_SnarkArgsHelper.default.md#getrevealargs) - [setSnarkCacheSize](Backend_Utils_SnarkArgsHelper.default.md#setsnarkcachesize) - [create](Backend_Utils_SnarkArgsHelper.default.md#create) ## Constructors ### constructor • `Private` **new default**(`hashConfig`, `terminal`, `useMockHash`) #### Parameters | Name | Type | | :------------ | :-------------------------------------------------------------------------------------------------------------- | | `hashConfig` | [`HashConfig`](../modules/types_global_GlobalTypes.md#hashconfig) | | `terminal` | `MutableRefObject`<`undefined` \| [`TerminalHandle`](../interfaces/Frontend_Views_Terminal.TerminalHandle.md)\> | | `useMockHash` | `boolean` | ## Properties ### biomebasePerlinOpts • `Private` `Readonly` **biomebasePerlinOpts**: `PerlinConfig` --- ### hashConfig • `Private` `Readonly` **hashConfig**: [`HashConfig`](../modules/types_global_GlobalTypes.md#hashconfig) --- ### moveSnarkCache • `Private` **moveSnarkCache**: `default`<`string`, `MoveSnarkContractCallArgs`\> --- ### planetHashMimc • `Private` `Readonly` **planetHashMimc**: (...`inputs`: `number`[]) => `BigInteger` #### Type declaration ▸ (...`inputs`): `BigInteger` ##### Parameters | Name | Type | | :---------- | :--------- | | `...inputs` | `number`[] | ##### Returns `BigInteger` --- ### snarkProverQueue • `Private` `Readonly` **snarkProverQueue**: `SnarkProverQueue` --- ### spaceTypePerlinOpts • `Private` `Readonly` **spaceTypePerlinOpts**: `PerlinConfig` --- ### terminal • `Private` `Readonly` **terminal**: `MutableRefObject`<`undefined` \| [`TerminalHandle`](../interfaces/Frontend_Views_Terminal.TerminalHandle.md)\> --- ### useMockHash • `Private` `Readonly` **useMockHash**: `boolean` --- ### DEFAULT_SNARK_CACHE_SIZE ▪ `Static` `Private` `Readonly` **DEFAULT_SNARK_CACHE_SIZE**: `20` How many snark results to keep in an LRU cache. ## Methods ### fakeBiomebaseProof ▸ `Private` **fakeBiomebaseProof**(`x`, `y`): `SnarkJSProofAndSignals` #### Parameters | Name | Type | | :--- | :------- | | `x` | `number` | | `y` | `number` | #### Returns `SnarkJSProofAndSignals` --- ### fakeInitProof ▸ `Private` **fakeInitProof**(`x`, `y`, `r`): `SnarkJSProofAndSignals` #### Parameters | Name | Type | | :--- | :------- | | `x` | `number` | | `y` | `number` | | `r` | `number` | #### Returns `SnarkJSProofAndSignals` --- ### fakeMoveProof ▸ `Private` **fakeMoveProof**(`x1`, `y1`, `x2`, `y2`, `r`, `distMax`): `SnarkJSProofAndSignals` #### Parameters | Name | Type | | :-------- | :------- | | `x1` | `number` | | `y1` | `number` | | `x2` | `number` | | `y2` | `number` | | `r` | `number` | | `distMax` | `number` | #### Returns `SnarkJSProofAndSignals` --- ### fakeRevealProof ▸ `Private` **fakeRevealProof**(`x`, `y`): `SnarkJSProofAndSignals` #### Parameters | Name | Type | | :--- | :------- | | `x` | `number` | | `y` | `number` | #### Returns `SnarkJSProofAndSignals` --- ### getFindArtifactArgs ▸ **getFindArtifactArgs**(`x`, `y`): `Promise`<`BiomebaseSnarkContractCallArgs`\> #### Parameters | Name | Type | | :--- | :------- | | `x` | `number` | | `y` | `number` | #### Returns `Promise`<`BiomebaseSnarkContractCallArgs`\> --- ### getInitArgs ▸ **getInitArgs**(`x`, `y`, `r`): `Promise`<`InitSnarkContractCallArgs`\> #### Parameters | Name | Type | | :--- | :------- | | `x` | `number` | | `y` | `number` | | `r` | `number` | #### Returns `Promise`<`InitSnarkContractCallArgs`\> --- ### getMoveArgs ▸ **getMoveArgs**(`x1`, `y1`, `x2`, `y2`, `r`, `distMax`): `Promise`<`MoveSnarkContractCallArgs`\> #### Parameters | Name | Type | | :-------- | :------- | | `x1` | `number` | | `y1` | `number` | | `x2` | `number` | | `y2` | `number` | | `r` | `number` | | `distMax` | `number` | #### Returns `Promise`<`MoveSnarkContractCallArgs`\> --- ### getRevealArgs ▸ **getRevealArgs**(`x`, `y`): `Promise`<`RevealSnarkContractCallArgs`\> #### Parameters | Name | Type | | :--- | :------- | | `x` | `number` | | `y` | `number` | #### Returns `Promise`<`RevealSnarkContractCallArgs`\> --- ### setSnarkCacheSize ▸ **setSnarkCacheSize**(`size`): `void` #### Parameters | Name | Type | | :----- | :------- | | `size` | `number` | #### Returns `void` --- ### create ▸ `Static` **create**(`hashConfig`, `terminal`, `fakeHash?`): [`default`](Backend_Utils_SnarkArgsHelper.default.md) #### Parameters | Name | Type | Default value | | :----------- | :-------------------------------------------------------------------------------------------------------------- | :------------ | | `hashConfig` | [`HashConfig`](../modules/types_global_GlobalTypes.md#hashconfig) | `undefined` | | `terminal` | `MutableRefObject`<`undefined` \| [`TerminalHandle`](../interfaces/Frontend_Views_Terminal.TerminalHandle.md)\> | `undefined` | | `fakeHash` | `boolean` | `false` | #### Returns [`default`](Backend_Utils_SnarkArgsHelper.default.md) ================================================ FILE: docs/classes/Backend_Utils_Wrapper.Wrapper.md ================================================ # Class: Wrapper [Backend/Utils/Wrapper](../modules/Backend_Utils_Wrapper.md).Wrapper React uses referential identity to detect changes, and rerender. Rather than copying an object into a new object, to force a rerender, we can just wrap it in a new {@code Wrapper}, which will force a rerender. ## Type parameters | Name | | :--- | | `T` | ## Table of contents ### Constructors - [constructor](Backend_Utils_Wrapper.Wrapper.md#constructor) ### Properties - [value](Backend_Utils_Wrapper.Wrapper.md#value) ## Constructors ### constructor • **new Wrapper**<`T`\>(`value`) #### Type parameters | Name | | :--- | | `T` | #### Parameters | Name | Type | | :------ | :--- | | `value` | `T` | ## Properties ### value • `Readonly` **value**: `T` ================================================ FILE: docs/classes/Frontend_Components_Btn.DarkForestButton.md ================================================ # Class: DarkForestButton [Frontend/Components/Btn](../modules/Frontend_Components_Btn.md).DarkForestButton ## Hierarchy - `LitElement` ↳ **`DarkForestButton`** ↳↳ [`DarkForestShortcutButton`](Frontend_Components_Btn.DarkForestShortcutButton.md) ## Table of contents ### Constructors - [constructor](Frontend_Components_Btn.DarkForestButton.md#constructor) ### Properties - [active](Frontend_Components_Btn.DarkForestButton.md#active) - [disabled](Frontend_Components_Btn.DarkForestButton.md#disabled) - [size](Frontend_Components_Btn.DarkForestButton.md#size) - [variant](Frontend_Components_Btn.DarkForestButton.md#variant) - [properties](Frontend_Components_Btn.DarkForestButton.md#properties) - [styles](Frontend_Components_Btn.DarkForestButton.md#styles) - [tagName](Frontend_Components_Btn.DarkForestButton.md#tagname) ### Methods - [\_handleClick](Frontend_Components_Btn.DarkForestButton.md#_handleclick) - [render](Frontend_Components_Btn.DarkForestButton.md#render) ## Constructors ### constructor • **new DarkForestButton**() #### Inherited from LitElement.constructor ## Properties ### active • **active**: `boolean` --- ### disabled • **disabled**: `boolean` --- ### size • **size**: `"small"` \| `"stretch"` \| `"medium"` \| `"large"` --- ### variant • **variant**: `"normal"` \| `"danger"` --- ### properties ▪ `Static` **properties**: `Object` #### Type declaration | Name | Type | | :-------------- | :------------------------------- | | `active` | { `type`: `BooleanConstructor` } | | `active.type` | `BooleanConstructor` | | `disabled` | { `type`: `BooleanConstructor` } | | `disabled.type` | `BooleanConstructor` | | `size` | { `type`: `StringConstructor` } | | `size.type` | `StringConstructor` | | `variant` | { `type`: `StringConstructor` } | | `variant.type` | `StringConstructor` | #### Overrides LitElement.properties --- ### styles ▪ `Static` **styles**: `CSSResult`[] #### Overrides LitElement.styles --- ### tagName ▪ `Static` **tagName**: `string` ## Methods ### \_handleClick ▸ `Protected` **\_handleClick**(`evt`): `void` #### Parameters | Name | Type | | :---- | :----------- | | `evt` | `MouseEvent` | #### Returns `void` --- ### render ▸ **render**(): `TemplateResult`<`1`\> #### Returns `TemplateResult`<`1`\> #### Overrides LitElement.render ================================================ FILE: docs/classes/Frontend_Components_Btn.DarkForestShortcutButton.md ================================================ # Class: DarkForestShortcutButton [Frontend/Components/Btn](../modules/Frontend_Components_Btn.md).DarkForestShortcutButton ## Hierarchy - [`DarkForestButton`](Frontend_Components_Btn.DarkForestButton.md) ↳ **`DarkForestShortcutButton`** ## Table of contents ### Constructors - [constructor](Frontend_Components_Btn.DarkForestShortcutButton.md#constructor) ### Properties - [\_getKeyFromEvent](Frontend_Components_Btn.DarkForestShortcutButton.md#_getkeyfromevent) - [\_handleKeyDown](Frontend_Components_Btn.DarkForestShortcutButton.md#_handlekeydown) - [\_handleKeyUp](Frontend_Components_Btn.DarkForestShortcutButton.md#_handlekeyup) - [\_renderKbd](Frontend_Components_Btn.DarkForestShortcutButton.md#_renderkbd) - [\_shortcutPressed](Frontend_Components_Btn.DarkForestShortcutButton.md#_shortcutpressed) - [active](Frontend_Components_Btn.DarkForestShortcutButton.md#active) - [disabled](Frontend_Components_Btn.DarkForestShortcutButton.md#disabled) - [shortcutKey](Frontend_Components_Btn.DarkForestShortcutButton.md#shortcutkey) - [shortcutText](Frontend_Components_Btn.DarkForestShortcutButton.md#shortcuttext) - [size](Frontend_Components_Btn.DarkForestShortcutButton.md#size) - [variant](Frontend_Components_Btn.DarkForestShortcutButton.md#variant) - [properties](Frontend_Components_Btn.DarkForestShortcutButton.md#properties) - [styles](Frontend_Components_Btn.DarkForestShortcutButton.md#styles) - [tagName](Frontend_Components_Btn.DarkForestShortcutButton.md#tagname) ### Methods - [\_handleClick](Frontend_Components_Btn.DarkForestShortcutButton.md#_handleclick) - [connectedCallback](Frontend_Components_Btn.DarkForestShortcutButton.md#connectedcallback) - [disconnectedCallback](Frontend_Components_Btn.DarkForestShortcutButton.md#disconnectedcallback) - [render](Frontend_Components_Btn.DarkForestShortcutButton.md#render) ## Constructors ### constructor • **new DarkForestShortcutButton**() #### Inherited from [DarkForestButton](Frontend_Components_Btn.DarkForestButton.md).[constructor](Frontend_Components_Btn.DarkForestButton.md#constructor) ## Properties ### \_getKeyFromEvent • `Private` **\_getKeyFromEvent**: `any` --- ### \_handleKeyDown • `Private` **\_handleKeyDown**: `any` --- ### \_handleKeyUp • `Private` **\_handleKeyUp**: `any` --- ### \_renderKbd • `Private` **\_renderKbd**: `any` --- ### \_shortcutPressed • `Private` **\_shortcutPressed**: `any` --- ### active • **active**: `boolean` #### Inherited from [DarkForestButton](Frontend_Components_Btn.DarkForestButton.md).[active](Frontend_Components_Btn.DarkForestButton.md#active) --- ### disabled • **disabled**: `boolean` #### Inherited from [DarkForestButton](Frontend_Components_Btn.DarkForestButton.md).[disabled](Frontend_Components_Btn.DarkForestButton.md#disabled) --- ### shortcutKey • `Optional` **shortcutKey**: `string` The `shortcutKey` indicates which key this component listens for while it is mounted --- ### shortcutText • `Optional` **shortcutText**: `string` The `shortcutText` indicates the key should be displayed and with what text --- ### size • **size**: `"small"` \| `"stretch"` \| `"medium"` \| `"large"` #### Inherited from [DarkForestButton](Frontend_Components_Btn.DarkForestButton.md).[size](Frontend_Components_Btn.DarkForestButton.md#size) --- ### variant • **variant**: `"normal"` \| `"danger"` #### Inherited from [DarkForestButton](Frontend_Components_Btn.DarkForestButton.md).[variant](Frontend_Components_Btn.DarkForestButton.md#variant) --- ### properties ▪ `Static` **properties**: `Object` #### Type declaration | Name | Type | | :----------------------- | :------------------------------- | | `_shortcutPressed` | { `state`: `boolean` } | | `_shortcutPressed.state` | `boolean` | | `active` | { `type`: `BooleanConstructor` } | | `active.type` | `BooleanConstructor` | | `disabled` | { `type`: `BooleanConstructor` } | | `disabled.type` | `BooleanConstructor` | | `shortcutKey` | { `type`: `StringConstructor` } | | `shortcutKey.type` | `StringConstructor` | | `shortcutText` | { `type`: `StringConstructor` } | | `shortcutText.type` | `StringConstructor` | | `size` | { `type`: `StringConstructor` } | | `size.type` | `StringConstructor` | | `variant` | { `type`: `StringConstructor` } | | `variant.type` | `StringConstructor` | #### Overrides [DarkForestButton](Frontend_Components_Btn.DarkForestButton.md).[properties](Frontend_Components_Btn.DarkForestButton.md#properties) --- ### styles ▪ `Static` **styles**: `CSSResult`[] #### Overrides [DarkForestButton](Frontend_Components_Btn.DarkForestButton.md).[styles](Frontend_Components_Btn.DarkForestButton.md#styles) --- ### tagName ▪ `Static` **tagName**: `string` #### Overrides [DarkForestButton](Frontend_Components_Btn.DarkForestButton.md).[tagName](Frontend_Components_Btn.DarkForestButton.md#tagname) ## Methods ### \_handleClick ▸ `Protected` **\_handleClick**(`evt`): `void` #### Parameters | Name | Type | | :---- | :----------- | | `evt` | `MouseEvent` | #### Returns `void` #### Inherited from [DarkForestButton](Frontend_Components_Btn.DarkForestButton.md).[\_handleClick](Frontend_Components_Btn.DarkForestButton.md#_handleclick) --- ### connectedCallback ▸ **connectedCallback**(): `void` #### Returns `void` #### Overrides DarkForestButton.connectedCallback --- ### disconnectedCallback ▸ **disconnectedCallback**(): `void` #### Returns `void` #### Overrides DarkForestButton.disconnectedCallback --- ### render ▸ **render**(): `TemplateResult`<`1`\> #### Returns `TemplateResult`<`1`\> #### Overrides [DarkForestButton](Frontend_Components_Btn.DarkForestButton.md).[render](Frontend_Components_Btn.DarkForestButton.md#render) ================================================ FILE: docs/classes/Frontend_Components_Btn.ShortcutPressedEvent.md ================================================ # Class: ShortcutPressedEvent [Frontend/Components/Btn](../modules/Frontend_Components_Btn.md).ShortcutPressedEvent ## Hierarchy - `Event` ↳ **`ShortcutPressedEvent`** ## Table of contents ### Constructors - [constructor](Frontend_Components_Btn.ShortcutPressedEvent.md#constructor) ## Constructors ### constructor • **new ShortcutPressedEvent**() #### Overrides Event.constructor ================================================ FILE: docs/classes/Frontend_Components_Input.DarkForestCheckbox.md ================================================ # Class: DarkForestCheckbox [Frontend/Components/Input](../modules/Frontend_Components_Input.md).DarkForestCheckbox ## Hierarchy - `LitElement` ↳ **`DarkForestCheckbox`** ## Table of contents ### Constructors - [constructor](Frontend_Components_Input.DarkForestCheckbox.md#constructor) ### Properties - [\_handleInput](Frontend_Components_Input.DarkForestCheckbox.md#_handleinput) - [\_handleKeyDown](Frontend_Components_Input.DarkForestCheckbox.md#_handlekeydown) - [\_handleKeyUp](Frontend_Components_Input.DarkForestCheckbox.md#_handlekeyup) - [\_inputRef](Frontend_Components_Input.DarkForestCheckbox.md#_inputref) - [checked](Frontend_Components_Input.DarkForestCheckbox.md#checked) - [disabled](Frontend_Components_Input.DarkForestCheckbox.md#disabled) - [label](Frontend_Components_Input.DarkForestCheckbox.md#label) - [selected](Frontend_Components_Input.DarkForestCheckbox.md#selected) - [properties](Frontend_Components_Input.DarkForestCheckbox.md#properties) - [styles](Frontend_Components_Input.DarkForestCheckbox.md#styles) - [tagName](Frontend_Components_Input.DarkForestCheckbox.md#tagname) ### Methods - [firstUpdated](Frontend_Components_Input.DarkForestCheckbox.md#firstupdated) - [focus](Frontend_Components_Input.DarkForestCheckbox.md#focus) - [render](Frontend_Components_Input.DarkForestCheckbox.md#render) - [select](Frontend_Components_Input.DarkForestCheckbox.md#select) ## Constructors ### constructor • **new DarkForestCheckbox**() #### Inherited from LitElement.constructor ## Properties ### \_handleInput • `Private` **\_handleInput**: `any` --- ### \_handleKeyDown • `Private` **\_handleKeyDown**: `any` --- ### \_handleKeyUp • `Private` **\_handleKeyUp**: `any` --- ### \_inputRef • `Private` **\_inputRef**: `any` --- ### checked • **checked**: `boolean` --- ### disabled • `Optional` **disabled**: `boolean` --- ### label • `Optional` **label**: `string` --- ### selected • **selected**: `boolean` --- ### properties ▪ `Static` **properties**: `Object` #### Type declaration | Name | Type | | :-------------- | :------------------------------- | | `checked` | { `type`: `BooleanConstructor` } | | `checked.type` | `BooleanConstructor` | | `disabled` | { `type`: `BooleanConstructor` } | | `disabled.type` | `BooleanConstructor` | | `label` | { `type`: `StringConstructor` } | | `label.type` | `StringConstructor` | | `selected` | { `type`: `BooleanConstructor` } | | `selected.type` | `BooleanConstructor` | #### Overrides LitElement.properties --- ### styles ▪ `Static` **styles**: `CSSResult` #### Overrides LitElement.styles --- ### tagName ▪ `Static` **tagName**: `string` ## Methods ### firstUpdated ▸ **firstUpdated**(): `void` #### Returns `void` #### Overrides LitElement.firstUpdated --- ### focus ▸ **focus**(): `void` #### Returns `void` #### Overrides LitElement.focus --- ### render ▸ **render**(): `TemplateResult`<`1`\> #### Returns `TemplateResult`<`1`\> #### Overrides LitElement.render --- ### select ▸ **select**(): `void` #### Returns `void` ================================================ FILE: docs/classes/Frontend_Components_Input.DarkForestColorInput.md ================================================ # Class: DarkForestColorInput [Frontend/Components/Input](../modules/Frontend_Components_Input.md).DarkForestColorInput ## Hierarchy - `LitElement` ↳ **`DarkForestColorInput`** ## Table of contents ### Constructors - [constructor](Frontend_Components_Input.DarkForestColorInput.md#constructor) ### Properties - [\_handleInput](Frontend_Components_Input.DarkForestColorInput.md#_handleinput) - [\_handleKeyDown](Frontend_Components_Input.DarkForestColorInput.md#_handlekeydown) - [\_handleKeyUp](Frontend_Components_Input.DarkForestColorInput.md#_handlekeyup) - [\_inputRef](Frontend_Components_Input.DarkForestColorInput.md#_inputref) - [disabled](Frontend_Components_Input.DarkForestColorInput.md#disabled) - [readonly](Frontend_Components_Input.DarkForestColorInput.md#readonly) - [selected](Frontend_Components_Input.DarkForestColorInput.md#selected) - [value](Frontend_Components_Input.DarkForestColorInput.md#value) - [properties](Frontend_Components_Input.DarkForestColorInput.md#properties) - [styles](Frontend_Components_Input.DarkForestColorInput.md#styles) - [tagName](Frontend_Components_Input.DarkForestColorInput.md#tagname) ### Methods - [firstUpdated](Frontend_Components_Input.DarkForestColorInput.md#firstupdated) - [focus](Frontend_Components_Input.DarkForestColorInput.md#focus) - [render](Frontend_Components_Input.DarkForestColorInput.md#render) - [select](Frontend_Components_Input.DarkForestColorInput.md#select) ## Constructors ### constructor • **new DarkForestColorInput**() #### Inherited from LitElement.constructor ## Properties ### \_handleInput • `Private` **\_handleInput**: `any` --- ### \_handleKeyDown • `Private` **\_handleKeyDown**: `any` --- ### \_handleKeyUp • `Private` **\_handleKeyUp**: `any` --- ### \_inputRef • `Private` **\_inputRef**: `any` --- ### disabled • `Optional` **disabled**: `boolean` --- ### readonly • **readonly**: `boolean` --- ### selected • **selected**: `boolean` --- ### value • **value**: `string` --- ### properties ▪ `Static` **properties**: `Object` #### Type declaration | Name | Type | | :-------------- | :------------------------------- | | `disabled` | { `type`: `BooleanConstructor` } | | `disabled.type` | `BooleanConstructor` | | `readonly` | { `type`: `BooleanConstructor` } | | `readonly.type` | `BooleanConstructor` | | `selected` | { `type`: `BooleanConstructor` } | | `selected.type` | `BooleanConstructor` | | `value` | { `type`: `StringConstructor` } | | `value.type` | `StringConstructor` | #### Overrides LitElement.properties --- ### styles ▪ `Static` **styles**: `CSSResult` #### Overrides LitElement.styles --- ### tagName ▪ `Static` **tagName**: `string` ## Methods ### firstUpdated ▸ **firstUpdated**(): `void` #### Returns `void` #### Overrides LitElement.firstUpdated --- ### focus ▸ **focus**(): `void` #### Returns `void` #### Overrides LitElement.focus --- ### render ▸ **render**(): `TemplateResult`<`1`\> #### Returns `TemplateResult`<`1`\> #### Overrides LitElement.render --- ### select ▸ **select**(): `void` #### Returns `void` ================================================ FILE: docs/classes/Frontend_Components_Input.DarkForestNumberInput.md ================================================ # Class: DarkForestNumberInput [Frontend/Components/Input](../modules/Frontend_Components_Input.md).DarkForestNumberInput ## Hierarchy - `LitElement` ↳ **`DarkForestNumberInput`** ## Table of contents ### Constructors - [constructor](Frontend_Components_Input.DarkForestNumberInput.md#constructor) ### Properties - [\_handleInput](Frontend_Components_Input.DarkForestNumberInput.md#_handleinput) - [\_handleKeyDown](Frontend_Components_Input.DarkForestNumberInput.md#_handlekeydown) - [\_handleKeyUp](Frontend_Components_Input.DarkForestNumberInput.md#_handlekeyup) - [\_handleWheel](Frontend_Components_Input.DarkForestNumberInput.md#_handlewheel) - [\_inputRef](Frontend_Components_Input.DarkForestNumberInput.md#_inputref) - [\_value](Frontend_Components_Input.DarkForestNumberInput.md#_value) - [disabled](Frontend_Components_Input.DarkForestNumberInput.md#disabled) - [format](Frontend_Components_Input.DarkForestNumberInput.md#format) - [readonly](Frontend_Components_Input.DarkForestNumberInput.md#readonly) - [selected](Frontend_Components_Input.DarkForestNumberInput.md#selected) - [properties](Frontend_Components_Input.DarkForestNumberInput.md#properties) - [styles](Frontend_Components_Input.DarkForestNumberInput.md#styles) - [tagName](Frontend_Components_Input.DarkForestNumberInput.md#tagname) ### Accessors - [value](Frontend_Components_Input.DarkForestNumberInput.md#value) ### Methods - [firstUpdated](Frontend_Components_Input.DarkForestNumberInput.md#firstupdated) - [focus](Frontend_Components_Input.DarkForestNumberInput.md#focus) - [render](Frontend_Components_Input.DarkForestNumberInput.md#render) - [select](Frontend_Components_Input.DarkForestNumberInput.md#select) ## Constructors ### constructor • **new DarkForestNumberInput**() #### Inherited from LitElement.constructor ## Properties ### \_handleInput • `Private` **\_handleInput**: `any` --- ### \_handleKeyDown • `Private` **\_handleKeyDown**: `any` --- ### \_handleKeyUp • `Private` **\_handleKeyUp**: `any` --- ### \_handleWheel • `Private` **\_handleWheel**: `any` --- ### \_inputRef • `Private` **\_inputRef**: `any` --- ### \_value • `Private` **\_value**: `any` --- ### disabled • `Optional` **disabled**: `boolean` --- ### format • **format**: `"integer"` \| `"float"` --- ### readonly • **readonly**: `boolean` --- ### selected • **selected**: `boolean` --- ### properties ▪ `Static` **properties**: `Object` #### Type declaration | Name | Type | | :-------------- | :------------------------------- | | `_value` | { `state`: `boolean` } | | `_value.state` | `boolean` | | `disabled` | { `type`: `BooleanConstructor` } | | `disabled.type` | `BooleanConstructor` | | `format` | { `type`: `StringConstructor` } | | `format.type` | `StringConstructor` | | `readonly` | { `type`: `BooleanConstructor` } | | `readonly.type` | `BooleanConstructor` | | `selected` | { `type`: `BooleanConstructor` } | | `selected.type` | `BooleanConstructor` | | `value` | { `type`: `NumberConstructor` } | | `value.type` | `NumberConstructor` | #### Overrides LitElement.properties --- ### styles ▪ `Static` **styles**: `CSSResult` #### Overrides LitElement.styles --- ### tagName ▪ `Static` **tagName**: `string` ## Accessors ### value • `get` **value**(): `undefined` \| `number` #### Returns `undefined` \| `number` • `set` **value**(`newValue`): `void` #### Parameters | Name | Type | | :--------- | :---------------------- | | `newValue` | `undefined` \| `number` | #### Returns `void` ## Methods ### firstUpdated ▸ **firstUpdated**(): `void` #### Returns `void` #### Overrides LitElement.firstUpdated --- ### focus ▸ **focus**(): `void` #### Returns `void` #### Overrides LitElement.focus --- ### render ▸ **render**(): `TemplateResult`<`1`\> #### Returns `TemplateResult`<`1`\> #### Overrides LitElement.render --- ### select ▸ **select**(): `void` #### Returns `void` ================================================ FILE: docs/classes/Frontend_Components_Input.DarkForestTextInput.md ================================================ # Class: DarkForestTextInput [Frontend/Components/Input](../modules/Frontend_Components_Input.md).DarkForestTextInput ## Hierarchy - `LitElement` ↳ **`DarkForestTextInput`** ## Table of contents ### Constructors - [constructor](Frontend_Components_Input.DarkForestTextInput.md#constructor) ### Properties - [\_handleInput](Frontend_Components_Input.DarkForestTextInput.md#_handleinput) - [\_handleKeyDown](Frontend_Components_Input.DarkForestTextInput.md#_handlekeydown) - [\_handleKeyUp](Frontend_Components_Input.DarkForestTextInput.md#_handlekeyup) - [\_inputRef](Frontend_Components_Input.DarkForestTextInput.md#_inputref) - [disabled](Frontend_Components_Input.DarkForestTextInput.md#disabled) - [placeholder](Frontend_Components_Input.DarkForestTextInput.md#placeholder) - [readonly](Frontend_Components_Input.DarkForestTextInput.md#readonly) - [selected](Frontend_Components_Input.DarkForestTextInput.md#selected) - [value](Frontend_Components_Input.DarkForestTextInput.md#value) - [properties](Frontend_Components_Input.DarkForestTextInput.md#properties) - [styles](Frontend_Components_Input.DarkForestTextInput.md#styles) - [tagName](Frontend_Components_Input.DarkForestTextInput.md#tagname) ### Methods - [firstUpdated](Frontend_Components_Input.DarkForestTextInput.md#firstupdated) - [focus](Frontend_Components_Input.DarkForestTextInput.md#focus) - [render](Frontend_Components_Input.DarkForestTextInput.md#render) - [select](Frontend_Components_Input.DarkForestTextInput.md#select) ## Constructors ### constructor • **new DarkForestTextInput**() #### Inherited from LitElement.constructor ## Properties ### \_handleInput • `Private` **\_handleInput**: `any` --- ### \_handleKeyDown • `Private` **\_handleKeyDown**: `any` --- ### \_handleKeyUp • `Private` **\_handleKeyUp**: `any` --- ### \_inputRef • `Private` **\_inputRef**: `any` --- ### disabled • `Optional` **disabled**: `boolean` --- ### placeholder • **placeholder**: `string` --- ### readonly • **readonly**: `boolean` --- ### selected • **selected**: `boolean` --- ### value • **value**: `string` --- ### properties ▪ `Static` **properties**: `Object` #### Type declaration | Name | Type | | :----------------- | :------------------------------- | | `disabled` | { `type`: `BooleanConstructor` } | | `disabled.type` | `BooleanConstructor` | | `placeholder` | { `type`: `StringConstructor` } | | `placeholder.type` | `StringConstructor` | | `readonly` | { `type`: `BooleanConstructor` } | | `readonly.type` | `BooleanConstructor` | | `selected` | { `type`: `BooleanConstructor` } | | `selected.type` | `BooleanConstructor` | | `value` | { `type`: `StringConstructor` } | | `value.type` | `StringConstructor` | #### Overrides LitElement.properties --- ### styles ▪ `Static` **styles**: `CSSResult` #### Overrides LitElement.styles --- ### tagName ▪ `Static` **tagName**: `string` ## Methods ### firstUpdated ▸ **firstUpdated**(): `void` #### Returns `void` #### Overrides LitElement.firstUpdated --- ### focus ▸ **focus**(): `void` #### Returns `void` #### Overrides LitElement.focus --- ### render ▸ **render**(): `TemplateResult`<`1`\> #### Returns `TemplateResult`<`1`\> #### Overrides LitElement.render --- ### select ▸ **select**(): `void` #### Returns `void` ================================================ FILE: docs/classes/Frontend_Components_Modal.DarkForestModal.md ================================================ # Class: DarkForestModal [Frontend/Components/Modal](../modules/Frontend_Components_Modal.md).DarkForestModal ## Hierarchy - `LitElement` ↳ **`DarkForestModal`** ## Table of contents ### Constructors - [constructor](Frontend_Components_Modal.DarkForestModal.md#constructor) ### Properties - [\_coords](Frontend_Components_Modal.DarkForestModal.md#_coords) - [\_delCoords](Frontend_Components_Modal.DarkForestModal.md#_delcoords) - [\_dragging](Frontend_Components_Modal.DarkForestModal.md#_dragging) - [\_handleMouseMove](Frontend_Components_Modal.DarkForestModal.md#_handlemousemove) - [\_handleMoveEnd](Frontend_Components_Modal.DarkForestModal.md#_handlemoveend) - [\_handleResize](Frontend_Components_Modal.DarkForestModal.md#_handleresize) - [\_mousedownCoords](Frontend_Components_Modal.DarkForestModal.md#_mousedowncoords) - [\_setDragging](Frontend_Components_Modal.DarkForestModal.md#_setdragging) - [\_unsetDragging](Frontend_Components_Modal.DarkForestModal.md#_unsetdragging) - [contain](Frontend_Components_Modal.DarkForestModal.md#contain) - [index](Frontend_Components_Modal.DarkForestModal.md#index) - [initialX](Frontend_Components_Modal.DarkForestModal.md#initialx) - [initialY](Frontend_Components_Modal.DarkForestModal.md#initialy) - [minimized](Frontend_Components_Modal.DarkForestModal.md#minimized) - [renderContent](Frontend_Components_Modal.DarkForestModal.md#rendercontent) - [renderTitleBar](Frontend_Components_Modal.DarkForestModal.md#rendertitlebar) - [width](Frontend_Components_Modal.DarkForestModal.md#width) - [properties](Frontend_Components_Modal.DarkForestModal.md#properties) - [styles](Frontend_Components_Modal.DarkForestModal.md#styles) - [tagName](Frontend_Components_Modal.DarkForestModal.md#tagname) ### Methods - [connectedCallback](Frontend_Components_Modal.DarkForestModal.md#connectedcallback) - [disconnectedCallback](Frontend_Components_Modal.DarkForestModal.md#disconnectedcallback) - [firstUpdated](Frontend_Components_Modal.DarkForestModal.md#firstupdated) - [render](Frontend_Components_Modal.DarkForestModal.md#render) - [updated](Frontend_Components_Modal.DarkForestModal.md#updated) ## Constructors ### constructor • **new DarkForestModal**() #### Inherited from LitElement.constructor ## Properties ### \_coords • `Private` `Optional` **\_coords**: `any` --- ### \_delCoords • `Private` `Optional` **\_delCoords**: `any` --- ### \_dragging • `Private` **\_dragging**: `any` --- ### \_handleMouseMove • `Private` **\_handleMouseMove**: `any` --- ### \_handleMoveEnd • `Private` **\_handleMoveEnd**: `any` --- ### \_handleResize • `Private` **\_handleResize**: `any` --- ### \_mousedownCoords • `Private` `Optional` **\_mousedownCoords**: `any` --- ### \_setDragging • `Private` **\_setDragging**: `any` --- ### \_unsetDragging • `Private` **\_unsetDragging**: `any` --- ### contain • **contain**: `Contain`[] --- ### index • `Optional` **index**: `number` --- ### initialX • `Optional` **initialX**: `number` --- ### initialY • `Optional` **initialY**: `number` --- ### minimized • **minimized**: `boolean` --- ### renderContent • `Private` **renderContent**: `any` --- ### renderTitleBar • `Private` **renderTitleBar**: `any` --- ### width • `Optional` **width**: `string` --- ### properties ▪ `Static` **properties**: `Object` #### Type declaration | Name | Type | | :----------------------- | :------------------------------- | | `_delCoords` | { `state`: `boolean` } | | `_delCoords.state` | `boolean` | | `_dragging` | { `state`: `boolean` } | | `_dragging.state` | `boolean` | | `_mousedownCoords` | { `state`: `boolean` } | | `_mousedownCoords.state` | `boolean` | | `contain` | { `type`: `ArrayConstructor` } | | `contain.type` | `ArrayConstructor` | | `index` | { `type`: `NumberConstructor` } | | `index.type` | `NumberConstructor` | | `initialX` | { `type`: `NumberConstructor` } | | `initialX.type` | `NumberConstructor` | | `initialY` | { `type`: `NumberConstructor` } | | `initialY.type` | `NumberConstructor` | | `minimized` | { `type`: `BooleanConstructor` } | | `minimized.type` | `BooleanConstructor` | | `width` | { `type`: `StringConstructor` } | | `width.type` | `StringConstructor` | #### Overrides LitElement.properties --- ### styles ▪ `Static` **styles**: `CSSResult` #### Overrides LitElement.styles --- ### tagName ▪ `Static` **tagName**: `string` ## Methods ### connectedCallback ▸ **connectedCallback**(): `void` #### Returns `void` #### Overrides LitElement.connectedCallback --- ### disconnectedCallback ▸ **disconnectedCallback**(): `void` #### Returns `void` #### Overrides LitElement.disconnectedCallback --- ### firstUpdated ▸ **firstUpdated**(): `void` #### Returns `void` #### Overrides LitElement.firstUpdated --- ### render ▸ **render**(): `TemplateResult`<`1`\> #### Returns `TemplateResult`<`1`\> #### Overrides LitElement.render --- ### updated ▸ **updated**(`changedProperties`): `void` #### Parameters | Name | Type | | :------------------ | :-------------------------------------------------- | | `changedProperties` | `Map`<`string` \| `number` \| `symbol`, `unknown`\> | #### Returns `void` #### Overrides LitElement.updated ================================================ FILE: docs/classes/Frontend_Components_Modal.PositionChangedEvent.md ================================================ # Class: PositionChangedEvent [Frontend/Components/Modal](../modules/Frontend_Components_Modal.md).PositionChangedEvent ## Hierarchy - `Event` ↳ **`PositionChangedEvent`** ## Table of contents ### Constructors - [constructor](Frontend_Components_Modal.PositionChangedEvent.md#constructor) ### Properties - [coords](Frontend_Components_Modal.PositionChangedEvent.md#coords) ## Constructors ### constructor • **new PositionChangedEvent**(`x`, `y`) #### Parameters | Name | Type | | :--- | :------- | | `x` | `number` | | `y` | `number` | #### Overrides Event.constructor ## Properties ### coords • **coords**: `Coords` ================================================ FILE: docs/classes/Frontend_Components_Slider.DarkForestSlider.md ================================================ # Class: DarkForestSlider [Frontend/Components/Slider](../modules/Frontend_Components_Slider.md).DarkForestSlider ## Hierarchy - `Slider` ↳ **`DarkForestSlider`** ## Table of contents ### Constructors - [constructor](Frontend_Components_Slider.DarkForestSlider.md#constructor) ### Properties - [\_handleKeyDown](Frontend_Components_Slider.DarkForestSlider.md#_handlekeydown) - [\_handleKeyUp](Frontend_Components_Slider.DarkForestSlider.md#_handlekeyup) - [tagName](Frontend_Components_Slider.DarkForestSlider.md#tagname) ### Accessors - [styles](Frontend_Components_Slider.DarkForestSlider.md#styles) ### Methods - [connectedCallback](Frontend_Components_Slider.DarkForestSlider.md#connectedcallback) - [disconnectedCallback](Frontend_Components_Slider.DarkForestSlider.md#disconnectedcallback) - [handlePointerdown](Frontend_Components_Slider.DarkForestSlider.md#handlepointerdown) - [handlePointermove](Frontend_Components_Slider.DarkForestSlider.md#handlepointermove) - [handlePointerup](Frontend_Components_Slider.DarkForestSlider.md#handlepointerup) ## Constructors ### constructor • **new DarkForestSlider**() #### Inherited from Slider.constructor ## Properties ### \_handleKeyDown • `Private` **\_handleKeyDown**: `any` --- ### \_handleKeyUp • `Private` **\_handleKeyUp**: `any` --- ### tagName ▪ `Static` **tagName**: `string` ## Accessors ### styles • `Static` `get` **styles**(): `CSSResultArray` #### Returns `CSSResultArray` #### Overrides Slider.styles ## Methods ### connectedCallback ▸ **connectedCallback**(): `void` #### Returns `void` #### Overrides Slider.connectedCallback --- ### disconnectedCallback ▸ **disconnectedCallback**(): `void` #### Returns `void` #### Overrides Slider.disconnectedCallback --- ### handlePointerdown ▸ **handlePointerdown**(`event`): `void` #### Parameters | Name | Type | | :------ | :------------- | | `event` | `PointerEvent` | #### Returns `void` #### Overrides Slider.handlePointerdown --- ### handlePointermove ▸ **handlePointermove**(`event`): `void` #### Parameters | Name | Type | | :------ | :------------- | | `event` | `PointerEvent` | #### Returns `void` #### Overrides Slider.handlePointermove --- ### handlePointerup ▸ **handlePointerup**(`event`): `void` #### Parameters | Name | Type | | :------ | :------------- | | `event` | `PointerEvent` | #### Returns `void` #### Overrides Slider.handlePointerup ================================================ FILE: docs/classes/Frontend_Components_Slider.DarkForestSliderHandle.md ================================================ # Class: DarkForestSliderHandle [Frontend/Components/Slider](../modules/Frontend_Components_Slider.md).DarkForestSliderHandle ## Hierarchy - `SliderHandle` ↳ **`DarkForestSliderHandle`** ## Table of contents ### Constructors - [constructor](Frontend_Components_Slider.DarkForestSliderHandle.md#constructor) ### Properties - [\_handleChange](Frontend_Components_Slider.DarkForestSliderHandle.md#_handlechange) - [tagName](Frontend_Components_Slider.DarkForestSliderHandle.md#tagname) ### Methods - [connectedCallback](Frontend_Components_Slider.DarkForestSliderHandle.md#connectedcallback) - [disconnectedCallback](Frontend_Components_Slider.DarkForestSliderHandle.md#disconnectedcallback) ## Constructors ### constructor • **new DarkForestSliderHandle**() #### Inherited from SliderHandle.constructor ## Properties ### \_handleChange • `Private` **\_handleChange**: `any` --- ### tagName ▪ `Static` **tagName**: `string` ## Methods ### connectedCallback ▸ **connectedCallback**(): `void` #### Returns `void` #### Overrides SliderHandle.connectedCallback --- ### disconnectedCallback ▸ **disconnectedCallback**(): `void` #### Returns `void` #### Overrides SliderHandle.disconnectedCallback ================================================ FILE: docs/classes/Frontend_Game_ModalManager.default.md ================================================ # Class: default [Frontend/Game/ModalManager](../modules/Frontend_Game_ModalManager.md).default ## Hierarchy - `EventEmitter` ↳ **`default`** ## Table of contents ### Constructors - [constructor](Frontend_Game_ModalManager.default.md#constructor) ### Properties - [activeModalId$](Frontend_Game_ModalManager.default.md#activemodalid$) - [cursorState](Frontend_Game_ModalManager.default.md#cursorstate) - [lastIndex](Frontend_Game_ModalManager.default.md#lastindex) - [modalPositionChanged$](Frontend_Game_ModalManager.default.md#modalpositionchanged$) - [modalPositions](Frontend_Game_ModalManager.default.md#modalpositions) - [modalPositions$](Frontend_Game_ModalManager.default.md#modalpositions$) - [persistentChunkStore](Frontend_Game_ModalManager.default.md#persistentchunkstore) - [instance](Frontend_Game_ModalManager.default.md#instance) ### Methods - [acceptInputForTarget](Frontend_Game_ModalManager.default.md#acceptinputfortarget) - [clearModalPosition](Frontend_Game_ModalManager.default.md#clearmodalposition) - [getCursorState](Frontend_Game_ModalManager.default.md#getcursorstate) - [getIndex](Frontend_Game_ModalManager.default.md#getindex) - [getModalPosition](Frontend_Game_ModalManager.default.md#getmodalposition) - [getModalPositions](Frontend_Game_ModalManager.default.md#getmodalpositions) - [setCursorState](Frontend_Game_ModalManager.default.md#setcursorstate) - [setModalPosition](Frontend_Game_ModalManager.default.md#setmodalposition) - [setModalState](Frontend_Game_ModalManager.default.md#setmodalstate) - [create](Frontend_Game_ModalManager.default.md#create) ## Constructors ### constructor • `Private` **new default**(`persistentChunkStore`, `modalPositions`) #### Parameters | Name | Type | | :--------------------- | :----------------------------------------------------------- | | `persistentChunkStore` | [`default`](Backend_Storage_PersistentChunkStore.default.md) | | `modalPositions` | `Map`<`ModalId`, `ModalPosition`\> | #### Overrides EventEmitter.constructor ## Properties ### activeModalId$ • `Readonly` **activeModalId$**: `Monomitter`<`string`\> --- ### cursorState • `Private` **cursorState**: `CursorState` --- ### lastIndex • `Private` **lastIndex**: `number` --- ### modalPositionChanged$ • `Readonly` **modalPositionChanged$**: `Monomitter`<`ModalId`\> --- ### modalPositions • `Private` **modalPositions**: `Map`<`ModalId`, `ModalPosition`\> --- ### modalPositions$ • **modalPositions$**: `Monomitter`<`Map`<`ModalId`, `ModalPosition`\>\> --- ### persistentChunkStore • `Private` **persistentChunkStore**: [`default`](Backend_Storage_PersistentChunkStore.default.md) --- ### instance ▪ `Static` **instance**: [`default`](Frontend_Game_ModalManager.default.md) ## Methods ### acceptInputForTarget ▸ **acceptInputForTarget**(`input`): `void` #### Parameters | Name | Type | | :------ | :------------ | | `input` | `WorldCoords` | #### Returns `void` --- ### clearModalPosition ▸ **clearModalPosition**(`modalId`): `void` #### Parameters | Name | Type | | :-------- | :-------- | | `modalId` | `ModalId` | #### Returns `void` --- ### getCursorState ▸ **getCursorState**(): `CursorState` #### Returns `CursorState` --- ### getIndex ▸ **getIndex**(): `number` #### Returns `number` --- ### getModalPosition ▸ **getModalPosition**(`modalId`): `undefined` \| `ModalPosition` #### Parameters | Name | Type | | :-------- | :-------- | | `modalId` | `ModalId` | #### Returns `undefined` \| `ModalPosition` --- ### getModalPositions ▸ **getModalPositions**(`modalIds?`): `Map`<`ModalId`, `ModalPosition`\> #### Parameters | Name | Type | Default value | | :--------- | :---------- | :------------ | | `modalIds` | `ModalId`[] | `[]` | #### Returns `Map`<`ModalId`, `ModalPosition`\> --- ### setCursorState ▸ **setCursorState**(`newstate`): `void` #### Parameters | Name | Type | | :--------- | :------------ | | `newstate` | `CursorState` | #### Returns `void` --- ### setModalPosition ▸ **setModalPosition**(`modalId`, `pos`): `void` #### Parameters | Name | Type | | :-------- | :-------------- | | `modalId` | `ModalId` | | `pos` | `ModalPosition` | #### Returns `void` --- ### setModalState ▸ **setModalState**(`modalId`, `state`): `void` #### Parameters | Name | Type | | :-------- | :-------------------------------------- | | `modalId` | `ModalId` | | `state` | `"open"` \| `"minimized"` \| `"closed"` | #### Returns `void` --- ### create ▸ `Static` **create**(`persistentChunkStore`): `Promise`<[`default`](Frontend_Game_ModalManager.default.md)\> #### Parameters | Name | Type | | :--------------------- | :----------------------------------------------------------- | | `persistentChunkStore` | [`default`](Backend_Storage_PersistentChunkStore.default.md) | #### Returns `Promise`<[`default`](Frontend_Game_ModalManager.default.md)\> ================================================ FILE: docs/classes/Frontend_Game_NotificationManager.default.md ================================================ # Class: default [Frontend/Game/NotificationManager](../modules/Frontend_Game_NotificationManager.md).default ## Hierarchy - `EventEmitter` ↳ **`default`** ## Table of contents ### Constructors - [constructor](Frontend_Game_NotificationManager.default.md#constructor) ### Properties - [instance](Frontend_Game_NotificationManager.default.md#instance) ### Methods - [artifactFound](Frontend_Game_NotificationManager.default.md#artifactfound) - [artifactProspected](Frontend_Game_NotificationManager.default.md#artifactprospected) - [balanceEmpty](Frontend_Game_NotificationManager.default.md#balanceempty) - [clearNotification](Frontend_Game_NotificationManager.default.md#clearnotification) - [foundBiome](Frontend_Game_NotificationManager.default.md#foundbiome) - [foundComet](Frontend_Game_NotificationManager.default.md#foundcomet) - [foundDeadSpace](Frontend_Game_NotificationManager.default.md#founddeadspace) - [foundDeepSpace](Frontend_Game_NotificationManager.default.md#founddeepspace) - [foundFoundry](Frontend_Game_NotificationManager.default.md#foundfoundry) - [foundPirates](Frontend_Game_NotificationManager.default.md#foundpirates) - [foundSilver](Frontend_Game_NotificationManager.default.md#foundsilver) - [foundSilverBank](Frontend_Game_NotificationManager.default.md#foundsilverbank) - [foundSpace](Frontend_Game_NotificationManager.default.md#foundspace) - [foundTradingPost](Frontend_Game_NotificationManager.default.md#foundtradingpost) - [getIcon](Frontend_Game_NotificationManager.default.md#geticon) - [notify](Frontend_Game_NotificationManager.default.md#notify) - [planetAttacked](Frontend_Game_NotificationManager.default.md#planetattacked) - [planetCanUpgrade](Frontend_Game_NotificationManager.default.md#planetcanupgrade) - [planetConquered](Frontend_Game_NotificationManager.default.md#planetconquered) - [planetLost](Frontend_Game_NotificationManager.default.md#planetlost) - [reallyLongNotification](Frontend_Game_NotificationManager.default.md#reallylongnotification) - [receivedPlanet](Frontend_Game_NotificationManager.default.md#receivedplanet) - [txInitError](Frontend_Game_NotificationManager.default.md#txiniterror) - [welcomePlayer](Frontend_Game_NotificationManager.default.md#welcomeplayer) - [getInstance](Frontend_Game_NotificationManager.default.md#getinstance) ## Constructors ### constructor • `Private` **new default**() #### Overrides EventEmitter.constructor ## Properties ### instance ▪ `Static` **instance**: [`default`](Frontend_Game_NotificationManager.default.md) ## Methods ### artifactFound ▸ **artifactFound**(`planet`, `artifact`): `void` #### Parameters | Name | Type | | :--------- | :---------------- | | `planet` | `LocatablePlanet` | | `artifact` | `Artifact` | #### Returns `void` --- ### artifactProspected ▸ **artifactProspected**(`planet`): `void` #### Parameters | Name | Type | | :------- | :---------------- | | `planet` | `LocatablePlanet` | #### Returns `void` --- ### balanceEmpty ▸ **balanceEmpty**(): `void` #### Returns `void` --- ### clearNotification ▸ **clearNotification**(`id`): `void` #### Parameters | Name | Type | | :--- | :------- | | `id` | `string` | #### Returns `void` --- ### foundBiome ▸ **foundBiome**(`planet`): `void` #### Parameters | Name | Type | | :------- | :---------------- | | `planet` | `LocatablePlanet` | #### Returns `void` --- ### foundComet ▸ **foundComet**(`planet`): `void` #### Parameters | Name | Type | | :------- | :------- | | `planet` | `Planet` | #### Returns `void` --- ### foundDeadSpace ▸ **foundDeadSpace**(`chunk`): `void` #### Parameters | Name | Type | | :------ | :------ | | `chunk` | `Chunk` | #### Returns `void` --- ### foundDeepSpace ▸ **foundDeepSpace**(`chunk`): `void` #### Parameters | Name | Type | | :------ | :------ | | `chunk` | `Chunk` | #### Returns `void` --- ### foundFoundry ▸ **foundFoundry**(`planet`): `void` #### Parameters | Name | Type | | :------- | :---------------- | | `planet` | `LocatablePlanet` | #### Returns `void` --- ### foundPirates ▸ **foundPirates**(`planet`): `void` #### Parameters | Name | Type | | :------- | :------- | | `planet` | `Planet` | #### Returns `void` --- ### foundSilver ▸ **foundSilver**(`planet`): `void` #### Parameters | Name | Type | | :------- | :------- | | `planet` | `Planet` | #### Returns `void` --- ### foundSilverBank ▸ **foundSilverBank**(`planet`): `void` #### Parameters | Name | Type | | :------- | :------- | | `planet` | `Planet` | #### Returns `void` --- ### foundSpace ▸ **foundSpace**(`chunk`): `void` #### Parameters | Name | Type | | :------ | :------ | | `chunk` | `Chunk` | #### Returns `void` --- ### foundTradingPost ▸ **foundTradingPost**(`planet`): `void` #### Parameters | Name | Type | | :------- | :------- | | `planet` | `Planet` | #### Returns `void` --- ### getIcon ▸ `Private` **getIcon**(`type`): `Element` #### Parameters | Name | Type | | :----- | :----------------------------------------------------------------------------------- | | `type` | [`NotificationType`](../enums/Frontend_Game_NotificationManager.NotificationType.md) | #### Returns `Element` --- ### notify ▸ **notify**(`type`, `message`): `void` #### Parameters | Name | Type | | :-------- | :----------------------------------------------------------------------------------- | | `type` | [`NotificationType`](../enums/Frontend_Game_NotificationManager.NotificationType.md) | | `message` | `ReactNode` | #### Returns `void` --- ### planetAttacked ▸ **planetAttacked**(`planet`): `void` #### Parameters | Name | Type | | :------- | :---------------- | | `planet` | `LocatablePlanet` | #### Returns `void` --- ### planetCanUpgrade ▸ **planetCanUpgrade**(`planet`): `void` #### Parameters | Name | Type | | :------- | :------- | | `planet` | `Planet` | #### Returns `void` --- ### planetConquered ▸ **planetConquered**(`planet`): `void` #### Parameters | Name | Type | | :------- | :---------------- | | `planet` | `LocatablePlanet` | #### Returns `void` --- ### planetLost ▸ **planetLost**(`planet`): `void` #### Parameters | Name | Type | | :------- | :---------------- | | `planet` | `LocatablePlanet` | #### Returns `void` --- ### reallyLongNotification ▸ **reallyLongNotification**(): `void` #### Returns `void` --- ### receivedPlanet ▸ **receivedPlanet**(`planet`): `void` #### Parameters | Name | Type | | :------- | :------- | | `planet` | `Planet` | #### Returns `void` --- ### txInitError ▸ **txInitError**(`methodName`, `failureReason`): `void` #### Parameters | Name | Type | | :-------------- | :------------------- | | `methodName` | `ContractMethodName` | | `failureReason` | `string` | #### Returns `void` --- ### welcomePlayer ▸ **welcomePlayer**(): `void` #### Returns `void` --- ### getInstance ▸ `Static` **getInstance**(): [`default`](Frontend_Game_NotificationManager.default.md) #### Returns [`default`](Frontend_Game_NotificationManager.default.md) ================================================ FILE: docs/classes/Frontend_Game_Viewport.default.md ================================================ # Class: default [Frontend/Game/Viewport](../modules/Frontend_Game_Viewport.md).default ## Table of contents ### Constructors - [constructor](Frontend_Game_Viewport.default.md#constructor) ### Properties - [canvas](Frontend_Game_Viewport.default.md#canvas) - [centerWorldCoords](Frontend_Game_Viewport.default.md#centerworldcoords) - [diagnosticUpdater](Frontend_Game_Viewport.default.md#diagnosticupdater) - [frameRequestId](Frontend_Game_Viewport.default.md#framerequestid) - [gameUIManager](Frontend_Game_Viewport.default.md#gameuimanager) - [heightInWorldUnits](Frontend_Game_Viewport.default.md#heightinworldunits) - [intervalId](Frontend_Game_Viewport.default.md#intervalid) - [isFirefox](Frontend_Game_Viewport.default.md#isfirefox) - [isPanning](Frontend_Game_Viewport.default.md#ispanning) - [isSending](Frontend_Game_Viewport.default.md#issending) - [momentum](Frontend_Game_Viewport.default.md#momentum) - [mouseLastCoords](Frontend_Game_Viewport.default.md#mouselastcoords) - [mouseSensitivity](Frontend_Game_Viewport.default.md#mousesensitivity) - [mousedownCoords](Frontend_Game_Viewport.default.md#mousedowncoords) - [scale](Frontend_Game_Viewport.default.md#scale) - [velocity](Frontend_Game_Viewport.default.md#velocity) - [viewportHeight](Frontend_Game_Viewport.default.md#viewportheight) - [viewportWidth](Frontend_Game_Viewport.default.md#viewportwidth) - [widthInWorldUnits](Frontend_Game_Viewport.default.md#widthinworldunits) - [instance](Frontend_Game_Viewport.default.md#instance) ### Accessors - [maxWorldWidth](Frontend_Game_Viewport.default.md#maxworldwidth) - [minWorldWidth](Frontend_Game_Viewport.default.md#minworldwidth) ### Methods - [canvasToWorldCoords](Frontend_Game_Viewport.default.md#canvastoworldcoords) - [canvasToWorldDist](Frontend_Game_Viewport.default.md#canvastoworlddist) - [canvasToWorldX](Frontend_Game_Viewport.default.md#canvastoworldx) - [canvasToWorldY](Frontend_Game_Viewport.default.md#canvastoworldy) - [centerChunk](Frontend_Game_Viewport.default.md#centerchunk) - [centerCoords](Frontend_Game_Viewport.default.md#centercoords) - [centerPlanet](Frontend_Game_Viewport.default.md#centerplanet) - [getBottomBound](Frontend_Game_Viewport.default.md#getbottombound) - [getLeftBound](Frontend_Game_Viewport.default.md#getleftbound) - [getRightBound](Frontend_Game_Viewport.default.md#getrightbound) - [getStorage](Frontend_Game_Viewport.default.md#getstorage) - [getStorageKey](Frontend_Game_Viewport.default.md#getstoragekey) - [getTopBound](Frontend_Game_Viewport.default.md#gettopbound) - [getViewportPosition](Frontend_Game_Viewport.default.md#getviewportposition) - [getViewportWorldHeight](Frontend_Game_Viewport.default.md#getviewportworldheight) - [getViewportWorldWidth](Frontend_Game_Viewport.default.md#getviewportworldwidth) - [intersectsViewport](Frontend_Game_Viewport.default.md#intersectsviewport) - [isInOrAroundViewport](Frontend_Game_Viewport.default.md#isinoraroundviewport) - [isInViewport](Frontend_Game_Viewport.default.md#isinviewport) - [isValidWorldWidth](Frontend_Game_Viewport.default.md#isvalidworldwidth) - [onMouseDown](Frontend_Game_Viewport.default.md#onmousedown) - [onMouseMove](Frontend_Game_Viewport.default.md#onmousemove) - [onMouseOut](Frontend_Game_Viewport.default.md#onmouseout) - [onMouseUp](Frontend_Game_Viewport.default.md#onmouseup) - [onResize](Frontend_Game_Viewport.default.md#onresize) - [onScroll](Frontend_Game_Viewport.default.md#onscroll) - [onSendComplete](Frontend_Game_Viewport.default.md#onsendcomplete) - [onSendInit](Frontend_Game_Viewport.default.md#onsendinit) - [onWindowResize](Frontend_Game_Viewport.default.md#onwindowresize) - [setData](Frontend_Game_Viewport.default.md#setdata) - [setDiagnosticUpdater](Frontend_Game_Viewport.default.md#setdiagnosticupdater) - [setMouseSensitivty](Frontend_Game_Viewport.default.md#setmousesensitivty) - [setStorage](Frontend_Game_Viewport.default.md#setstorage) - [setWorldHeight](Frontend_Game_Viewport.default.md#setworldheight) - [setWorldWidth](Frontend_Game_Viewport.default.md#setworldwidth) - [updateDiagnostics](Frontend_Game_Viewport.default.md#updatediagnostics) - [worldToCanvasCoords](Frontend_Game_Viewport.default.md#worldtocanvascoords) - [worldToCanvasDist](Frontend_Game_Viewport.default.md#worldtocanvasdist) - [worldToCanvasX](Frontend_Game_Viewport.default.md#worldtocanvasx) - [worldToCanvasY](Frontend_Game_Viewport.default.md#worldtocanvasy) - [zoomIn](Frontend_Game_Viewport.default.md#zoomin) - [zoomOut](Frontend_Game_Viewport.default.md#zoomout) - [zoomPlanet](Frontend_Game_Viewport.default.md#zoomplanet) - [destroyInstance](Frontend_Game_Viewport.default.md#destroyinstance) - [getInstance](Frontend_Game_Viewport.default.md#getinstance) - [initialize](Frontend_Game_Viewport.default.md#initialize) ## Constructors ### constructor • `Private` **new default**(`gameUIManager`, `centerWorldCoords`, `widthInWorldUnits`, `viewportWidth`, `viewportHeight`, `canvas`) #### Parameters | Name | Type | | :------------------ | :------------------------------------------------------ | | `gameUIManager` | [`default`](Backend_GameLogic_GameUIManager.default.md) | | `centerWorldCoords` | `WorldCoords` | | `widthInWorldUnits` | `number` | | `viewportWidth` | `number` | | `viewportHeight` | `number` | | `canvas` | `HTMLCanvasElement` | ## Properties ### canvas • **canvas**: `HTMLCanvasElement` --- ### centerWorldCoords • **centerWorldCoords**: `WorldCoords` --- ### diagnosticUpdater • `Optional` **diagnosticUpdater**: `DiagnosticUpdater` --- ### frameRequestId • **frameRequestId**: `number` --- ### gameUIManager • **gameUIManager**: [`default`](Backend_GameLogic_GameUIManager.default.md) --- ### heightInWorldUnits • **heightInWorldUnits**: `number` --- ### intervalId • **intervalId**: `Timeout` --- ### isFirefox • **isFirefox**: `boolean` --- ### isPanning • **isPanning**: `boolean` = `false` --- ### isSending • `Private` **isSending**: `boolean` = `false` --- ### momentum • **momentum**: `boolean` = `false` --- ### mouseLastCoords • **mouseLastCoords**: `undefined` \| `CanvasCoords` --- ### mouseSensitivity • **mouseSensitivity**: `number` --- ### mousedownCoords • **mousedownCoords**: `undefined` \| `CanvasCoords` = `undefined` --- ### scale • **scale**: `number` --- ### velocity • **velocity**: `undefined` \| `WorldCoords` = `undefined` --- ### viewportHeight • **viewportHeight**: `number` --- ### viewportWidth • **viewportWidth**: `number` --- ### widthInWorldUnits • **widthInWorldUnits**: `number` --- ### instance ▪ `Static` **instance**: `undefined` \| [`default`](Frontend_Game_Viewport.default.md) ## Accessors ### maxWorldWidth • `get` **maxWorldWidth**(): `number` #### Returns `number` --- ### minWorldWidth • `get` **minWorldWidth**(): `number` #### Returns `number` ## Methods ### canvasToWorldCoords ▸ **canvasToWorldCoords**(`canvasCoords`): `WorldCoords` #### Parameters | Name | Type | | :------------- | :------------- | | `canvasCoords` | `CanvasCoords` | #### Returns `WorldCoords` --- ### canvasToWorldDist ▸ **canvasToWorldDist**(`d`): `number` #### Parameters | Name | Type | | :--- | :------- | | `d` | `number` | #### Returns `number` --- ### canvasToWorldX ▸ `Private` **canvasToWorldX**(`x`): `number` #### Parameters | Name | Type | | :--- | :------- | | `x` | `number` | #### Returns `number` --- ### canvasToWorldY ▸ `Private` **canvasToWorldY**(`y`): `number` #### Parameters | Name | Type | | :--- | :------- | | `y` | `number` | #### Returns `number` --- ### centerChunk ▸ **centerChunk**(`chunk`): `void` #### Parameters | Name | Type | | :------ | :------ | | `chunk` | `Chunk` | #### Returns `void` --- ### centerCoords ▸ **centerCoords**(`coords`): `void` #### Parameters | Name | Type | | :------- | :------------ | | `coords` | `WorldCoords` | #### Returns `void` --- ### centerPlanet ▸ **centerPlanet**(`planet`): `void` #### Parameters | Name | Type | | :------- | :---------------------- | | `planet` | `undefined` \| `Planet` | #### Returns `void` --- ### getBottomBound ▸ **getBottomBound**(): `number` #### Returns `number` --- ### getLeftBound ▸ **getLeftBound**(): `number` #### Returns `number` --- ### getRightBound ▸ **getRightBound**(): `number` #### Returns `number` --- ### getStorage ▸ **getStorage**(): `undefined` \| `ViewportData` #### Returns `undefined` \| `ViewportData` --- ### getStorageKey ▸ `Private` **getStorageKey**(): `string` #### Returns `string` --- ### getTopBound ▸ **getTopBound**(): `number` #### Returns `number` --- ### getViewportPosition ▸ **getViewportPosition**(): `Object` #### Returns `Object` | Name | Type | | :--- | :------- | | `x` | `number` | | `y` | `number` | --- ### getViewportWorldHeight ▸ **getViewportWorldHeight**(): `number` #### Returns `number` --- ### getViewportWorldWidth ▸ **getViewportWorldWidth**(): `number` #### Returns `number` --- ### intersectsViewport ▸ **intersectsViewport**(`chunk`): `boolean` #### Parameters | Name | Type | | :------ | :------ | | `chunk` | `Chunk` | #### Returns `boolean` --- ### isInOrAroundViewport ▸ **isInOrAroundViewport**(`coords`): `boolean` #### Parameters | Name | Type | | :------- | :------------ | | `coords` | `WorldCoords` | #### Returns `boolean` --- ### isInViewport ▸ **isInViewport**(`coords`): `boolean` #### Parameters | Name | Type | | :------- | :------------ | | `coords` | `WorldCoords` | #### Returns `boolean` --- ### isValidWorldWidth ▸ `Private` **isValidWorldWidth**(`width`): `boolean` #### Parameters | Name | Type | | :------ | :------- | | `width` | `number` | #### Returns `boolean` --- ### onMouseDown ▸ **onMouseDown**(`canvasCoords`): `void` #### Parameters | Name | Type | | :------------- | :------------- | | `canvasCoords` | `CanvasCoords` | #### Returns `void` --- ### onMouseMove ▸ **onMouseMove**(`canvasCoords`): `void` #### Parameters | Name | Type | | :------------- | :------------- | | `canvasCoords` | `CanvasCoords` | #### Returns `void` --- ### onMouseOut ▸ **onMouseOut**(): `void` #### Returns `void` --- ### onMouseUp ▸ **onMouseUp**(`canvasCoords`): `void` #### Parameters | Name | Type | | :------------- | :------------- | | `canvasCoords` | `CanvasCoords` | #### Returns `void` --- ### onResize ▸ **onResize**(): `void` #### Returns `void` --- ### onScroll ▸ **onScroll**(`deltaY`, `forceZoom?`): `void` #### Parameters | Name | Type | Default value | | :---------- | :-------- | :------------ | | `deltaY` | `number` | `undefined` | | `forceZoom` | `boolean` | `false` | #### Returns `void` --- ### onSendComplete ▸ **onSendComplete**(): `void` #### Returns `void` --- ### onSendInit ▸ **onSendInit**(): `void` #### Returns `void` --- ### onWindowResize ▸ **onWindowResize**(): `void` #### Returns `void` --- ### setData ▸ **setData**(`data`): `void` #### Parameters | Name | Type | | :----- | :------------- | | `data` | `ViewportData` | #### Returns `void` --- ### setDiagnosticUpdater ▸ **setDiagnosticUpdater**(`diagnosticUpdater`): `void` #### Parameters | Name | Type | | :------------------ | :------------------ | | `diagnosticUpdater` | `DiagnosticUpdater` | #### Returns `void` --- ### setMouseSensitivty ▸ **setMouseSensitivty**(`mouseSensitivity`): `void` #### Parameters | Name | Type | | :----------------- | :------- | | `mouseSensitivity` | `number` | #### Returns `void` --- ### setStorage ▸ **setStorage**(): `void` #### Returns `void` --- ### setWorldHeight ▸ **setWorldHeight**(`height`): `void` #### Parameters | Name | Type | | :------- | :------- | | `height` | `number` | #### Returns `void` --- ### setWorldWidth ▸ `Private` **setWorldWidth**(`width`): `void` #### Parameters | Name | Type | | :------ | :------- | | `width` | `number` | #### Returns `void` --- ### updateDiagnostics ▸ `Private` **updateDiagnostics**(): `void` #### Returns `void` --- ### worldToCanvasCoords ▸ **worldToCanvasCoords**(`worldCoords`): `CanvasCoords` #### Parameters | Name | Type | | :------------ | :------------ | | `worldCoords` | `WorldCoords` | #### Returns `CanvasCoords` --- ### worldToCanvasDist ▸ **worldToCanvasDist**(`d`): `number` #### Parameters | Name | Type | | :--- | :------- | | `d` | `number` | #### Returns `number` --- ### worldToCanvasX ▸ `Private` **worldToCanvasX**(`x`): `number` #### Parameters | Name | Type | | :--- | :------- | | `x` | `number` | #### Returns `number` --- ### worldToCanvasY ▸ `Private` **worldToCanvasY**(`y`): `number` #### Parameters | Name | Type | | :--- | :------- | | `y` | `number` | #### Returns `number` --- ### zoomIn ▸ **zoomIn**(): `void` #### Returns `void` --- ### zoomOut ▸ **zoomOut**(): `void` #### Returns `void` --- ### zoomPlanet ▸ **zoomPlanet**(`planet?`, `radii?`): `void` #### Parameters | Name | Type | | :-------- | :------- | | `planet?` | `Planet` | | `radii?` | `number` | #### Returns `void` --- ### destroyInstance ▸ `Static` **destroyInstance**(): `void` #### Returns `void` --- ### getInstance ▸ `Static` **getInstance**(): [`default`](Frontend_Game_Viewport.default.md) #### Returns [`default`](Frontend_Game_Viewport.default.md) --- ### initialize ▸ `Static` **initialize**(`gameUIManager`, `widthInWorldUnits`, `canvas`): [`default`](Frontend_Game_Viewport.default.md) #### Parameters | Name | Type | | :------------------ | :------------------------------------------------------ | | `gameUIManager` | [`default`](Backend_GameLogic_GameUIManager.default.md) | | `widthInWorldUnits` | `number` | | `canvas` | `HTMLCanvasElement` | #### Returns [`default`](Frontend_Game_Viewport.default.md) ================================================ FILE: docs/classes/Frontend_Panes_Lobbies_Reducer.InvalidConfigError.md ================================================ # Class: InvalidConfigError [Frontend/Panes/Lobbies/Reducer](../modules/Frontend_Panes_Lobbies_Reducer.md).InvalidConfigError ## Hierarchy - `Error` ↳ **`InvalidConfigError`** ## Table of contents ### Constructors - [constructor](Frontend_Panes_Lobbies_Reducer.InvalidConfigError.md#constructor) ### Properties - [key](Frontend_Panes_Lobbies_Reducer.InvalidConfigError.md#key) - [value](Frontend_Panes_Lobbies_Reducer.InvalidConfigError.md#value) ## Constructors ### constructor • **new InvalidConfigError**(`msg`, `key`, `value`) #### Parameters | Name | Type | | :------ | :-------- | | `msg` | `string` | | `key` | `string` | | `value` | `unknown` | #### Overrides Error.constructor ## Properties ### key • **key**: `string` --- ### value • **value**: `unknown` ================================================ FILE: docs/classes/Frontend_Renderers_Artifacts_ArtifactRenderer.ArtifactRenderer.md ================================================ # Class: ArtifactRenderer [Frontend/Renderers/Artifacts/ArtifactRenderer](../modules/Frontend_Renderers_Artifacts_ArtifactRenderer.md).ArtifactRenderer ## Hierarchy - `WebGLManager` ↳ **`ArtifactRenderer`** ## Table of contents ### Constructors - [constructor](Frontend_Renderers_Artifacts_ArtifactRenderer.ArtifactRenderer.md#constructor) ### Properties - [artifacts](Frontend_Renderers_Artifacts_ArtifactRenderer.ArtifactRenderer.md#artifacts) - [canvas](Frontend_Renderers_Artifacts_ArtifactRenderer.ArtifactRenderer.md#canvas) - [frameRequestId](Frontend_Renderers_Artifacts_ArtifactRenderer.ArtifactRenderer.md#framerequestid) - [gl](Frontend_Renderers_Artifacts_ArtifactRenderer.ArtifactRenderer.md#gl) - [isDex](Frontend_Renderers_Artifacts_ArtifactRenderer.ArtifactRenderer.md#isdex) - [projectionMatrix](Frontend_Renderers_Artifacts_ArtifactRenderer.ArtifactRenderer.md#projectionmatrix) - [scroll](Frontend_Renderers_Artifacts_ArtifactRenderer.ArtifactRenderer.md#scroll) - [spriteRenderer](Frontend_Renderers_Artifacts_ArtifactRenderer.ArtifactRenderer.md#spriterenderer) - [visible](Frontend_Renderers_Artifacts_ArtifactRenderer.ArtifactRenderer.md#visible) ### Methods - [clear](Frontend_Renderers_Artifacts_ArtifactRenderer.ArtifactRenderer.md#clear) - [containsArtifact](Frontend_Renderers_Artifacts_ArtifactRenderer.ArtifactRenderer.md#containsartifact) - [destroy](Frontend_Renderers_Artifacts_ArtifactRenderer.ArtifactRenderer.md#destroy) - [draw](Frontend_Renderers_Artifacts_ArtifactRenderer.ArtifactRenderer.md#draw) - [drawDex](Frontend_Renderers_Artifacts_ArtifactRenderer.ArtifactRenderer.md#drawdex) - [drawList](Frontend_Renderers_Artifacts_ArtifactRenderer.ArtifactRenderer.md#drawlist) - [getTexIdx](Frontend_Renderers_Artifacts_ArtifactRenderer.ArtifactRenderer.md#gettexidx) - [loop](Frontend_Renderers_Artifacts_ArtifactRenderer.ArtifactRenderer.md#loop) - [queueArtifactColumn](Frontend_Renderers_Artifacts_ArtifactRenderer.ArtifactRenderer.md#queueartifactcolumn) - [queueRarityColumn](Frontend_Renderers_Artifacts_ArtifactRenderer.ArtifactRenderer.md#queueraritycolumn) - [setArtifacts](Frontend_Renderers_Artifacts_ArtifactRenderer.ArtifactRenderer.md#setartifacts) - [setIsDex](Frontend_Renderers_Artifacts_ArtifactRenderer.ArtifactRenderer.md#setisdex) - [setProjectionMatrix](Frontend_Renderers_Artifacts_ArtifactRenderer.ArtifactRenderer.md#setprojectionmatrix) - [setScroll](Frontend_Renderers_Artifacts_ArtifactRenderer.ArtifactRenderer.md#setscroll) - [setVisible](Frontend_Renderers_Artifacts_ArtifactRenderer.ArtifactRenderer.md#setvisible) ## Constructors ### constructor • **new ArtifactRenderer**(`canvas`, `isDex?`) #### Parameters | Name | Type | Default value | | :------- | :------------------ | :------------ | | `canvas` | `HTMLCanvasElement` | `undefined` | | `isDex` | `boolean` | `true` | #### Overrides WebGLManager.constructor ## Properties ### artifacts • `Private` **artifacts**: `Artifact`[] --- ### canvas • **canvas**: `HTMLCanvasElement` #### Inherited from WebGLManager.canvas --- ### frameRequestId • `Private` **frameRequestId**: `number` --- ### gl • **gl**: `WebGL2RenderingContext` #### Inherited from WebGLManager.gl --- ### isDex • `Private` **isDex**: `boolean` --- ### projectionMatrix • **projectionMatrix**: `mat4` #### Inherited from WebGLManager.projectionMatrix --- ### scroll • `Private` **scroll**: `number` = `0` --- ### spriteRenderer • `Private` **spriteRenderer**: `SpriteRenderer` --- ### visible • `Private` **visible**: `boolean` = `false` ## Methods ### clear ▸ **clear**(`bits?`, `color?`): `void` #### Parameters | Name | Type | | :------- | :-------- | | `bits?` | `number` | | `color?` | `RGBAVec` | #### Returns `void` #### Inherited from WebGLManager.clear --- ### containsArtifact ▸ `Private` **containsArtifact**(`biome`, `rarity`, `type`): `boolean` #### Parameters | Name | Type | | :------- | :--------------- | | `biome` | `Biome` | | `rarity` | `ArtifactRarity` | | `type` | `ArtifactType` | #### Returns `boolean` --- ### destroy ▸ **destroy**(): `void` #### Returns `void` --- ### draw ▸ `Private` **draw**(): `void` #### Returns `void` --- ### drawDex ▸ `Private` **drawDex**(): `void` #### Returns `void` --- ### drawList ▸ `Private` **drawList**(): `void` #### Returns `void` --- ### getTexIdx ▸ **getTexIdx**(): `number` #### Returns `number` #### Inherited from WebGLManager.getTexIdx --- ### loop ▸ `Private` **loop**(): `void` #### Returns `void` --- ### queueArtifactColumn ▸ `Private` **queueArtifactColumn**(`type`, `rarity`, `startX`): `void` #### Parameters | Name | Type | | :------- | :--------------- | | `type` | `ArtifactType` | | `rarity` | `ArtifactRarity` | | `startX` | `number` | #### Returns `void` --- ### queueRarityColumn ▸ `Private` **queueRarityColumn**(`rarity`, `startX`): `void` #### Parameters | Name | Type | | :------- | :--------------- | | `rarity` | `ArtifactRarity` | | `startX` | `number` | #### Returns `void` --- ### setArtifacts ▸ **setArtifacts**(`artifacts`): `void` #### Parameters | Name | Type | | :---------- | :----------- | | `artifacts` | `Artifact`[] | #### Returns `void` --- ### setIsDex ▸ **setIsDex**(`isDex`): `void` #### Parameters | Name | Type | | :------ | :-------- | | `isDex` | `boolean` | #### Returns `void` --- ### setProjectionMatrix ▸ **setProjectionMatrix**(): `void` #### Returns `void` #### Inherited from WebGLManager.setProjectionMatrix --- ### setScroll ▸ **setScroll**(`scroll`): `void` #### Parameters | Name | Type | | :------- | :------- | | `scroll` | `number` | #### Returns `void` --- ### setVisible ▸ **setVisible**(`visible`): `void` #### Parameters | Name | Type | | :-------- | :-------- | | `visible` | `boolean` | #### Returns `void` ================================================ FILE: docs/classes/Frontend_Renderers_GifRenderer.GifRenderer.md ================================================ # Class: GifRenderer [Frontend/Renderers/GifRenderer](../modules/Frontend_Renderers_GifRenderer.md).GifRenderer ## Hierarchy - `WebGLManager` ↳ **`GifRenderer`** ## Table of contents ### Constructors - [constructor](Frontend_Renderers_GifRenderer.GifRenderer.md#constructor) ### Properties - [artifactDim](Frontend_Renderers_GifRenderer.GifRenderer.md#artifactdim) - [canvas](Frontend_Renderers_GifRenderer.GifRenderer.md#canvas) - [canvasDim](Frontend_Renderers_GifRenderer.GifRenderer.md#canvasdim) - [gl](Frontend_Renderers_GifRenderer.GifRenderer.md#gl) - [margin](Frontend_Renderers_GifRenderer.GifRenderer.md#margin) - [projectionMatrix](Frontend_Renderers_GifRenderer.GifRenderer.md#projectionmatrix) - [resolution](Frontend_Renderers_GifRenderer.GifRenderer.md#resolution) - [spriteRenderer](Frontend_Renderers_GifRenderer.GifRenderer.md#spriterenderer) - [thumb](Frontend_Renderers_GifRenderer.GifRenderer.md#thumb) ### Methods - [addAncient](Frontend_Renderers_GifRenderer.GifRenderer.md#addancient) - [addBiomes](Frontend_Renderers_GifRenderer.GifRenderer.md#addbiomes) - [addSprite](Frontend_Renderers_GifRenderer.GifRenderer.md#addsprite) - [addVideo](Frontend_Renderers_GifRenderer.GifRenderer.md#addvideo) - [clear](Frontend_Renderers_GifRenderer.GifRenderer.md#clear) - [drawSprite](Frontend_Renderers_GifRenderer.GifRenderer.md#drawsprite) - [getAll](Frontend_Renderers_GifRenderer.GifRenderer.md#getall) - [getAllSprites](Frontend_Renderers_GifRenderer.GifRenderer.md#getallsprites) - [getAllVideos](Frontend_Renderers_GifRenderer.GifRenderer.md#getallvideos) - [getBase64](Frontend_Renderers_GifRenderer.GifRenderer.md#getbase64) - [getFileName](Frontend_Renderers_GifRenderer.GifRenderer.md#getfilename) - [getTexIdx](Frontend_Renderers_GifRenderer.GifRenderer.md#gettexidx) - [setDim](Frontend_Renderers_GifRenderer.GifRenderer.md#setdim) - [setProjectionMatrix](Frontend_Renderers_GifRenderer.GifRenderer.md#setprojectionmatrix) ## Constructors ### constructor • **new GifRenderer**(`canvas`, `dim`, `isThumb`) #### Parameters | Name | Type | | :-------- | :------------------ | | `canvas` | `HTMLCanvasElement` | | `dim` | `number` | | `isThumb` | `boolean` | #### Overrides WebGLManager.constructor ## Properties ### artifactDim • `Private` **artifactDim**: `number` --- ### canvas • **canvas**: `HTMLCanvasElement` #### Inherited from WebGLManager.canvas --- ### canvasDim • `Private` **canvasDim**: `number` --- ### gl • **gl**: `WebGL2RenderingContext` #### Inherited from WebGLManager.gl --- ### margin • `Private` **margin**: `number` --- ### projectionMatrix • **projectionMatrix**: `mat4` #### Overrides WebGLManager.projectionMatrix --- ### resolution • `Private` **resolution**: `number` --- ### spriteRenderer • `Private` **spriteRenderer**: `SpriteRenderer` --- ### thumb • `Private` **thumb**: `boolean` ## Methods ### addAncient ▸ `Private` **addAncient**(`videoMode`, `dir`): `Promise`<`void`\> #### Parameters | Name | Type | | :---------- | :-------- | | `videoMode` | `boolean` | | `dir` | `JSZip` | #### Returns `Promise`<`void`\> --- ### addBiomes ▸ `Private` **addBiomes**(`videoMode`, `dir`): `Promise`<`void`\> #### Parameters | Name | Type | | :---------- | :-------- | | `videoMode` | `boolean` | | `dir` | `JSZip` | #### Returns `Promise`<`void`\> --- ### addSprite ▸ `Private` **addSprite**(`dir`, `type`, `biome`, `rarity`, `ancient?`): `void` #### Parameters | Name | Type | Default value | | :-------- | :--------------- | :------------ | | `dir` | `JSZip` | `undefined` | | `type` | `ArtifactType` | `undefined` | | `biome` | `Biome` | `undefined` | | `rarity` | `ArtifactRarity` | `undefined` | | `ancient` | `boolean` | `false` | #### Returns `void` --- ### addVideo ▸ `Private` **addVideo**(`dir`, `type`, `biome`, `rarity`, `ancient?`): `Promise`<`void`\> #### Parameters | Name | Type | Default value | | :-------- | :--------------- | :------------ | | `dir` | `JSZip` | `undefined` | | `type` | `ArtifactType` | `undefined` | | `biome` | `Biome` | `undefined` | | `rarity` | `ArtifactRarity` | `undefined` | | `ancient` | `boolean` | `false` | #### Returns `Promise`<`void`\> --- ### clear ▸ **clear**(): `void` #### Returns `void` #### Overrides WebGLManager.clear --- ### drawSprite ▸ `Private` **drawSprite**(`artifact`, `atFrame?`): `void` #### Parameters | Name | Type | Default value | | :--------- | :---------------------- | :------------ | | `artifact` | `Artifact` | `undefined` | | `atFrame` | `undefined` \| `number` | `undefined` | #### Returns `void` --- ### getAll ▸ `Private` **getAll**(`videoMode?`): `Promise`<`void`\> #### Parameters | Name | Type | Default value | | :---------- | :-------- | :------------ | | `videoMode` | `boolean` | `false` | #### Returns `Promise`<`void`\> --- ### getAllSprites ▸ **getAllSprites**(): `void` #### Returns `void` --- ### getAllVideos ▸ **getAllVideos**(): `void` #### Returns `void` --- ### getBase64 ▸ `Private` **getBase64**(): `string` #### Returns `string` --- ### getFileName ▸ `Private` **getFileName**(`video`, `type`, `biome`, `rarity`, `ancient`): `string` #### Parameters | Name | Type | | :-------- | :--------------- | | `video` | `boolean` | | `type` | `ArtifactType` | | `biome` | `Biome` | | `rarity` | `ArtifactRarity` | | `ancient` | `boolean` | #### Returns `string` --- ### getTexIdx ▸ **getTexIdx**(): `number` #### Returns `number` #### Inherited from WebGLManager.getTexIdx --- ### setDim ▸ `Private` **setDim**(`dim`): `void` #### Parameters | Name | Type | | :---- | :------- | | `dim` | `number` | #### Returns `void` --- ### setProjectionMatrix ▸ **setProjectionMatrix**(): `void` #### Returns `void` #### Inherited from WebGLManager.setProjectionMatrix ================================================ FILE: docs/classes/Frontend_Utils_UIEmitter.default.md ================================================ # Class: default [Frontend/Utils/UIEmitter](../modules/Frontend_Utils_UIEmitter.md).default ## Hierarchy - `EventEmitter` ↳ **`default`** ## Table of contents ### Constructors - [constructor](Frontend_Utils_UIEmitter.default.md#constructor) ### Properties - [instance](Frontend_Utils_UIEmitter.default.md#instance) ### Methods - [getInstance](Frontend_Utils_UIEmitter.default.md#getinstance) - [initialize](Frontend_Utils_UIEmitter.default.md#initialize) ## Constructors ### constructor • `Private` **new default**() #### Overrides EventEmitter.constructor ## Properties ### instance ▪ `Static` **instance**: [`default`](Frontend_Utils_UIEmitter.default.md) ## Methods ### getInstance ▸ `Static` **getInstance**(): [`default`](Frontend_Utils_UIEmitter.default.md) #### Returns [`default`](Frontend_Utils_UIEmitter.default.md) --- ### initialize ▸ `Static` **initialize**(): [`default`](Frontend_Utils_UIEmitter.default.md) #### Returns [`default`](Frontend_Utils_UIEmitter.default.md) ================================================ FILE: docs/classes/Frontend_Views_DFErrorBoundary.DFErrorBoundary.md ================================================ # Class: DFErrorBoundary [Frontend/Views/DFErrorBoundary](../modules/Frontend_Views_DFErrorBoundary.md).DFErrorBoundary ## Hierarchy - `Component`<`unknown`, { `hasError`: `boolean` }\> ↳ **`DFErrorBoundary`** ## Table of contents ### Constructors - [constructor](Frontend_Views_DFErrorBoundary.DFErrorBoundary.md#constructor) ### Methods - [componentDidCatch](Frontend_Views_DFErrorBoundary.DFErrorBoundary.md#componentdidcatch) - [render](Frontend_Views_DFErrorBoundary.DFErrorBoundary.md#render) - [getDerivedStateFromError](Frontend_Views_DFErrorBoundary.DFErrorBoundary.md#getderivedstatefromerror) ## Constructors ### constructor • **new DFErrorBoundary**(`props`) #### Parameters | Name | Type | | :------ | :-------- | | `props` | `unknown` | #### Overrides React.Component<unknown, { hasError: boolean }\>.constructor ## Methods ### componentDidCatch ▸ **componentDidCatch**(`error`, `_errorInfo`): `void` #### Parameters | Name | Type | | :----------- | :---------- | | `error` | `Error` | | `_errorInfo` | `ErrorInfo` | #### Returns `void` #### Overrides React.Component.componentDidCatch --- ### render ▸ **render**(): `ReactNode` #### Returns `ReactNode` #### Overrides React.Component.render --- ### getDerivedStateFromError ▸ `Static` **getDerivedStateFromError**(`_error`): `Object` #### Parameters | Name | Type | | :------- | :------ | | `_error` | `Error` | #### Returns `Object` | Name | Type | | :--------- | :-------- | | `hasError` | `boolean` | ================================================ FILE: docs/classes/Frontend_Views_GenericErrorBoundary.GenericErrorBoundary.md ================================================ # Class: GenericErrorBoundary [Frontend/Views/GenericErrorBoundary](../modules/Frontend_Views_GenericErrorBoundary.md).GenericErrorBoundary ## Hierarchy - `Component`<`GenericErrorBoundaryProps`, { `hasError`: `boolean` }\> ↳ **`GenericErrorBoundary`** ## Table of contents ### Constructors - [constructor](Frontend_Views_GenericErrorBoundary.GenericErrorBoundary.md#constructor) ### Methods - [componentDidCatch](Frontend_Views_GenericErrorBoundary.GenericErrorBoundary.md#componentdidcatch) - [render](Frontend_Views_GenericErrorBoundary.GenericErrorBoundary.md#render) - [getDerivedStateFromError](Frontend_Views_GenericErrorBoundary.GenericErrorBoundary.md#getderivedstatefromerror) ## Constructors ### constructor • **new GenericErrorBoundary**(`props`) #### Parameters | Name | Type | | :------ | :-------------------------- | | `props` | `GenericErrorBoundaryProps` | #### Overrides React.Component< GenericErrorBoundaryProps, { hasError: boolean } \>.constructor ## Methods ### componentDidCatch ▸ **componentDidCatch**(`error`, `_errorInfo`): `void` #### Parameters | Name | Type | | :----------- | :---------- | | `error` | `Error` | | `_errorInfo` | `ErrorInfo` | #### Returns `void` #### Overrides React.Component.componentDidCatch --- ### render ▸ **render**(): `ReactNode` #### Returns `ReactNode` #### Overrides React.Component.render --- ### getDerivedStateFromError ▸ `Static` **getDerivedStateFromError**(`_error`): `Object` #### Parameters | Name | Type | | :------- | :------ | | `_error` | `Error` | #### Returns `Object` | Name | Type | | :--------- | :-------- | | `hasError` | `boolean` | ================================================ FILE: docs/enums/Backend_GameLogic_GameManager.GameManagerEvent.md ================================================ # Enumeration: GameManagerEvent [Backend/GameLogic/GameManager](../modules/Backend_GameLogic_GameManager.md).GameManagerEvent ## Table of contents ### Enumeration members - [ArtifactUpdate](Backend_GameLogic_GameManager.GameManagerEvent.md#artifactupdate) - [DiscoveredNewChunk](Backend_GameLogic_GameManager.GameManagerEvent.md#discoverednewchunk) - [InitializedPlayer](Backend_GameLogic_GameManager.GameManagerEvent.md#initializedplayer) - [InitializedPlayerError](Backend_GameLogic_GameManager.GameManagerEvent.md#initializedplayererror) - [Moved](Backend_GameLogic_GameManager.GameManagerEvent.md#moved) - [PlanetUpdate](Backend_GameLogic_GameManager.GameManagerEvent.md#planetupdate) ## Enumeration members ### ArtifactUpdate • **ArtifactUpdate** = `"ArtifactUpdate"` --- ### DiscoveredNewChunk • **DiscoveredNewChunk** = `"DiscoveredNewChunk"` --- ### InitializedPlayer • **InitializedPlayer** = `"InitializedPlayer"` --- ### InitializedPlayerError • **InitializedPlayerError** = `"InitializedPlayerError"` --- ### Moved • **Moved** = `"Moved"` --- ### PlanetUpdate • **PlanetUpdate** = `"PlanetUpdate"` ================================================ FILE: docs/enums/Backend_GameLogic_GameUIManager.GameUIManagerEvent.md ================================================ # Enumeration: GameUIManagerEvent [Backend/GameLogic/GameUIManager](../modules/Backend_GameLogic_GameUIManager.md).GameUIManagerEvent ## Table of contents ### Enumeration members - [InitializedPlayer](Backend_GameLogic_GameUIManager.GameUIManagerEvent.md#initializedplayer) - [InitializedPlayerError](Backend_GameLogic_GameUIManager.GameUIManagerEvent.md#initializedplayererror) ## Enumeration members ### InitializedPlayer • **InitializedPlayer** = `"InitializedPlayer"` --- ### InitializedPlayerError • **InitializedPlayerError** = `"InitializedPlayerError"` ================================================ FILE: docs/enums/Backend_GameLogic_TutorialManager.TutorialManagerEvent.md ================================================ # Enumeration: TutorialManagerEvent [Backend/GameLogic/TutorialManager](../modules/Backend_GameLogic_TutorialManager.md).TutorialManagerEvent ## Table of contents ### Enumeration members - [StateChanged](Backend_GameLogic_TutorialManager.TutorialManagerEvent.md#statechanged) ## Enumeration members ### StateChanged • **StateChanged** = `"StateChanged"` ================================================ FILE: docs/enums/Backend_GameLogic_TutorialManager.TutorialState.md ================================================ # Enumeration: TutorialState [Backend/GameLogic/TutorialManager](../modules/Backend_GameLogic_TutorialManager.md).TutorialState ## Table of contents ### Enumeration members - [AlmostCompleted](Backend_GameLogic_TutorialManager.TutorialState.md#almostcompleted) - [Completed](Backend_GameLogic_TutorialManager.TutorialState.md#completed) - [Deselect](Backend_GameLogic_TutorialManager.TutorialState.md#deselect) - [HomePlanet](Backend_GameLogic_TutorialManager.TutorialState.md#homeplanet) - [HowToGetScore](Backend_GameLogic_TutorialManager.TutorialState.md#howtogetscore) - [MinerMove](Backend_GameLogic_TutorialManager.TutorialState.md#minermove) - [MinerPause](Backend_GameLogic_TutorialManager.TutorialState.md#minerpause) - [None](Backend_GameLogic_TutorialManager.TutorialState.md#none) - [ScoringDetails](Backend_GameLogic_TutorialManager.TutorialState.md#scoringdetails) - [SendFleet](Backend_GameLogic_TutorialManager.TutorialState.md#sendfleet) - [SpaceJunk](Backend_GameLogic_TutorialManager.TutorialState.md#spacejunk) - [Spaceship](Backend_GameLogic_TutorialManager.TutorialState.md#spaceship) - [Terminal](Backend_GameLogic_TutorialManager.TutorialState.md#terminal) - [Valhalla](Backend_GameLogic_TutorialManager.TutorialState.md#valhalla) - [ZoomOut](Backend_GameLogic_TutorialManager.TutorialState.md#zoomout) ## Enumeration members ### AlmostCompleted • **AlmostCompleted** = `13` --- ### Completed • **Completed** = `14` --- ### Deselect • **Deselect** = `5` --- ### HomePlanet • **HomePlanet** = `1` --- ### HowToGetScore • **HowToGetScore** = `10` --- ### MinerMove • **MinerMove** = `7` --- ### MinerPause • **MinerPause** = `8` --- ### None • **None** = `0` --- ### ScoringDetails • **ScoringDetails** = `11` --- ### SendFleet • **SendFleet** = `2` --- ### SpaceJunk • **SpaceJunk** = `3` --- ### Spaceship • **Spaceship** = `4` --- ### Terminal • **Terminal** = `9` --- ### Valhalla • **Valhalla** = `12` --- ### ZoomOut • **ZoomOut** = `6` ================================================ FILE: docs/enums/Backend_Miner_MinerManager.MinerManagerEvent.md ================================================ # Enumeration: MinerManagerEvent [Backend/Miner/MinerManager](../modules/Backend_Miner_MinerManager.md).MinerManagerEvent ## Table of contents ### Enumeration members - [DiscoveredNewChunk](Backend_Miner_MinerManager.MinerManagerEvent.md#discoverednewchunk) ## Enumeration members ### DiscoveredNewChunk • **DiscoveredNewChunk** = `"DiscoveredNewChunk"` ================================================ FILE: docs/enums/Backend_Miner_MiningPatterns.MiningPatternType.md ================================================ # Enumeration: MiningPatternType [Backend/Miner/MiningPatterns](../modules/Backend_Miner_MiningPatterns.md).MiningPatternType ## Table of contents ### Enumeration members - [Cone](Backend_Miner_MiningPatterns.MiningPatternType.md#cone) - [ETH](Backend_Miner_MiningPatterns.MiningPatternType.md#eth) - [Grid](Backend_Miner_MiningPatterns.MiningPatternType.md#grid) - [Home](Backend_Miner_MiningPatterns.MiningPatternType.md#home) - [Spiral](Backend_Miner_MiningPatterns.MiningPatternType.md#spiral) - [SwissCheese](Backend_Miner_MiningPatterns.MiningPatternType.md#swisscheese) - [Target](Backend_Miner_MiningPatterns.MiningPatternType.md#target) - [TowardsCenter](Backend_Miner_MiningPatterns.MiningPatternType.md#towardscenter) - [TowardsCenterV2](Backend_Miner_MiningPatterns.MiningPatternType.md#towardscenterv2) ## Enumeration members ### Cone • **Cone** = `3` --- ### ETH • **ETH** = `5` --- ### Grid • **Grid** = `4` --- ### Home • **Home** = `0` --- ### Spiral • **Spiral** = `2` --- ### SwissCheese • **SwissCheese** = `6` --- ### Target • **Target** = `1` --- ### TowardsCenter • **TowardsCenter** = `7` --- ### TowardsCenterV2 • **TowardsCenterV2** = `8` ================================================ FILE: docs/enums/Backend_Network_EventLogger.EventType.md ================================================ # Enumeration: EventType [Backend/Network/EventLogger](../modules/Backend_Network_EventLogger.md).EventType ## Table of contents ### Enumeration members - [Diagnostics](Backend_Network_EventLogger.EventType.md#diagnostics) - [Transaction](Backend_Network_EventLogger.EventType.md#transaction) ## Enumeration members ### Diagnostics • **Diagnostics** = `"diagnostics"` --- ### Transaction • **Transaction** = `"transaction"` ================================================ FILE: docs/enums/Backend_Network_UtilityServerAPI.EmailResponse.md ================================================ # Enumeration: EmailResponse [Backend/Network/UtilityServerAPI](../modules/Backend_Network_UtilityServerAPI.md).EmailResponse ## Table of contents ### Enumeration members - [Invalid](Backend_Network_UtilityServerAPI.EmailResponse.md#invalid) - [ServerError](Backend_Network_UtilityServerAPI.EmailResponse.md#servererror) - [Success](Backend_Network_UtilityServerAPI.EmailResponse.md#success) ## Enumeration members ### Invalid • **Invalid** = `1` --- ### ServerError • **ServerError** = `2` --- ### Success • **Success** = `0` ================================================ FILE: docs/enums/Backend_Storage_ReaderDataStore.SinglePlanetDataStoreEvent.md ================================================ # Enumeration: SinglePlanetDataStoreEvent [Backend/Storage/ReaderDataStore](../modules/Backend_Storage_ReaderDataStore.md).SinglePlanetDataStoreEvent ## Table of contents ### Enumeration members - [REFRESHED_ARTIFACT](Backend_Storage_ReaderDataStore.SinglePlanetDataStoreEvent.md#refreshed_artifact) - [REFRESHED_PLANET](Backend_Storage_ReaderDataStore.SinglePlanetDataStoreEvent.md#refreshed_planet) ## Enumeration members ### REFRESHED_ARTIFACT • **REFRESHED_ARTIFACT** = `"REFRESHED_ARTIFACT"` --- ### REFRESHED_PLANET • **REFRESHED_PLANET** = `"REFRESHED_PLANET"` ================================================ FILE: docs/enums/Frontend_Components_Email.EmailCTAMode.md ================================================ # Enumeration: EmailCTAMode [Frontend/Components/Email](../modules/Frontend_Components_Email.md).EmailCTAMode ## Table of contents ### Enumeration members - [SUBSCRIBE](Frontend_Components_Email.EmailCTAMode.md#subscribe) - [UNSUBSCRIBE](Frontend_Components_Email.EmailCTAMode.md#unsubscribe) ## Enumeration members ### SUBSCRIBE • **SUBSCRIBE** = `0` --- ### UNSUBSCRIBE • **UNSUBSCRIBE** = `1` ================================================ FILE: docs/enums/Frontend_Components_GameLandingPageComponents.InitRenderState.md ================================================ # Enumeration: InitRenderState [Frontend/Components/GameLandingPageComponents](../modules/Frontend_Components_GameLandingPageComponents.md).InitRenderState ## Table of contents ### Enumeration members - [COMPLETE](Frontend_Components_GameLandingPageComponents.InitRenderState.md#complete) - [LOADING](Frontend_Components_GameLandingPageComponents.InitRenderState.md#loading) - [NONE](Frontend_Components_GameLandingPageComponents.InitRenderState.md#none) ## Enumeration members ### COMPLETE • **COMPLETE** = `2` --- ### LOADING • **LOADING** = `1` --- ### NONE • **NONE** = `0` ================================================ FILE: docs/enums/Frontend_Game_NotificationManager.NotificationManagerEvent.md ================================================ # Enumeration: NotificationManagerEvent [Frontend/Game/NotificationManager](../modules/Frontend_Game_NotificationManager.md).NotificationManagerEvent ## Table of contents ### Enumeration members - [ClearNotification](Frontend_Game_NotificationManager.NotificationManagerEvent.md#clearnotification) - [Notify](Frontend_Game_NotificationManager.NotificationManagerEvent.md#notify) ## Enumeration members ### ClearNotification • **ClearNotification** = `"ClearNotification"` --- ### Notify • **Notify** = `"Notify"` ================================================ FILE: docs/enums/Frontend_Game_NotificationManager.NotificationType.md ================================================ # Enumeration: NotificationType [Frontend/Game/NotificationManager](../modules/Frontend_Game_NotificationManager.md).NotificationType ## Table of contents ### Enumeration members - [ArtifactFound](Frontend_Game_NotificationManager.NotificationType.md#artifactfound) - [ArtifactProspected](Frontend_Game_NotificationManager.NotificationType.md#artifactprospected) - [BalanceEmpty](Frontend_Game_NotificationManager.NotificationType.md#balanceempty) - [CanUpgrade](Frontend_Game_NotificationManager.NotificationType.md#canupgrade) - [FoundBiome](Frontend_Game_NotificationManager.NotificationType.md#foundbiome) - [FoundBiomeCorrupted](Frontend_Game_NotificationManager.NotificationType.md#foundbiomecorrupted) - [FoundBiomeDesert](Frontend_Game_NotificationManager.NotificationType.md#foundbiomedesert) - [FoundBiomeForest](Frontend_Game_NotificationManager.NotificationType.md#foundbiomeforest) - [FoundBiomeGrassland](Frontend_Game_NotificationManager.NotificationType.md#foundbiomegrassland) - [FoundBiomeIce](Frontend_Game_NotificationManager.NotificationType.md#foundbiomeice) - [FoundBiomeLava](Frontend_Game_NotificationManager.NotificationType.md#foundbiomelava) - [FoundBiomeOcean](Frontend_Game_NotificationManager.NotificationType.md#foundbiomeocean) - [FoundBiomeSwamp](Frontend_Game_NotificationManager.NotificationType.md#foundbiomeswamp) - [FoundBiomeTundra](Frontend_Game_NotificationManager.NotificationType.md#foundbiometundra) - [FoundBiomeWasteland](Frontend_Game_NotificationManager.NotificationType.md#foundbiomewasteland) - [FoundComet](Frontend_Game_NotificationManager.NotificationType.md#foundcomet) - [FoundDeadSpace](Frontend_Game_NotificationManager.NotificationType.md#founddeadspace) - [FoundDeepSpace](Frontend_Game_NotificationManager.NotificationType.md#founddeepspace) - [FoundFoundry](Frontend_Game_NotificationManager.NotificationType.md#foundfoundry) - [FoundPirates](Frontend_Game_NotificationManager.NotificationType.md#foundpirates) - [FoundSilver](Frontend_Game_NotificationManager.NotificationType.md#foundsilver) - [FoundSilverBank](Frontend_Game_NotificationManager.NotificationType.md#foundsilverbank) - [FoundSpace](Frontend_Game_NotificationManager.NotificationType.md#foundspace) - [FoundTradingPost](Frontend_Game_NotificationManager.NotificationType.md#foundtradingpost) - [Generic](Frontend_Game_NotificationManager.NotificationType.md#generic) - [PlanetAttacked](Frontend_Game_NotificationManager.NotificationType.md#planetattacked) - [PlanetLost](Frontend_Game_NotificationManager.NotificationType.md#planetlost) - [PlanetWon](Frontend_Game_NotificationManager.NotificationType.md#planetwon) - [ReceivedPlanet](Frontend_Game_NotificationManager.NotificationType.md#receivedplanet) - [Tx](Frontend_Game_NotificationManager.NotificationType.md#tx) - [TxInitError](Frontend_Game_NotificationManager.NotificationType.md#txiniterror) - [WelcomePlayer](Frontend_Game_NotificationManager.NotificationType.md#welcomeplayer) ## Enumeration members ### ArtifactFound • **ArtifactFound** = `28` --- ### ArtifactProspected • **ArtifactProspected** = `27` --- ### BalanceEmpty • **BalanceEmpty** = `2` --- ### CanUpgrade • **CanUpgrade** = `1` --- ### FoundBiome • **FoundBiome** = `13` --- ### FoundBiomeCorrupted • **FoundBiomeCorrupted** = `23` --- ### FoundBiomeDesert • **FoundBiomeDesert** = `19` --- ### FoundBiomeForest • **FoundBiomeForest** = `15` --- ### FoundBiomeGrassland • **FoundBiomeGrassland** = `16` --- ### FoundBiomeIce • **FoundBiomeIce** = `20` --- ### FoundBiomeLava • **FoundBiomeLava** = `22` --- ### FoundBiomeOcean • **FoundBiomeOcean** = `14` --- ### FoundBiomeSwamp • **FoundBiomeSwamp** = `18` --- ### FoundBiomeTundra • **FoundBiomeTundra** = `17` --- ### FoundBiomeWasteland • **FoundBiomeWasteland** = `21` --- ### FoundComet • **FoundComet** = `11` --- ### FoundDeadSpace • **FoundDeadSpace** = `6` --- ### FoundDeepSpace • **FoundDeepSpace** = `5` --- ### FoundFoundry • **FoundFoundry** = `12` --- ### FoundPirates • **FoundPirates** = `7` --- ### FoundSilver • **FoundSilver** = `8` --- ### FoundSilverBank • **FoundSilverBank** = `9` --- ### FoundSpace • **FoundSpace** = `4` --- ### FoundTradingPost • **FoundTradingPost** = `10` --- ### Generic • **Generic** = `30` --- ### PlanetAttacked • **PlanetAttacked** = `26` --- ### PlanetLost • **PlanetLost** = `24` --- ### PlanetWon • **PlanetWon** = `25` --- ### ReceivedPlanet • **ReceivedPlanet** = `29` --- ### Tx • **Tx** = `0` --- ### TxInitError • **TxInitError** = `31` --- ### WelcomePlayer • **WelcomePlayer** = `3` ================================================ FILE: docs/enums/Frontend_Pages_LandingPage.LandingPageZIndex.md ================================================ # Enumeration: LandingPageZIndex [Frontend/Pages/LandingPage](../modules/Frontend_Pages_LandingPage.md).LandingPageZIndex ## Table of contents ### Enumeration members - [Background](Frontend_Pages_LandingPage.LandingPageZIndex.md#background) - [BasePage](Frontend_Pages_LandingPage.LandingPageZIndex.md#basepage) - [Canvas](Frontend_Pages_LandingPage.LandingPageZIndex.md#canvas) ## Enumeration members ### Background • **Background** = `0` --- ### BasePage • **BasePage** = `2` --- ### Canvas • **Canvas** = `1` ================================================ FILE: docs/enums/Frontend_Pages_UnsubscribePage.LandingPageZIndex.md ================================================ # Enumeration: LandingPageZIndex [Frontend/Pages/UnsubscribePage](../modules/Frontend_Pages_UnsubscribePage.md).LandingPageZIndex ## Table of contents ### Enumeration members - [Background](Frontend_Pages_UnsubscribePage.LandingPageZIndex.md#background) - [BasePage](Frontend_Pages_UnsubscribePage.LandingPageZIndex.md#basepage) - [Canvas](Frontend_Pages_UnsubscribePage.LandingPageZIndex.md#canvas) ## Enumeration members ### Background • **Background** = `0` --- ### BasePage • **BasePage** = `2` --- ### Canvas • **Canvas** = `1` ================================================ FILE: docs/enums/Frontend_Utils_BrowserChecks.Incompatibility.md ================================================ # Enumeration: Incompatibility [Frontend/Utils/BrowserChecks](../modules/Frontend_Utils_BrowserChecks.md).Incompatibility ## Table of contents ### Enumeration members - [MobileOrTablet](Frontend_Utils_BrowserChecks.Incompatibility.md#mobileortablet) - [NoIDB](Frontend_Utils_BrowserChecks.Incompatibility.md#noidb) - [NotLoggedInOrEnabled](Frontend_Utils_BrowserChecks.Incompatibility.md#notloggedinorenabled) - [NotRopsten](Frontend_Utils_BrowserChecks.Incompatibility.md#notropsten) - [UnexpectedError](Frontend_Utils_BrowserChecks.Incompatibility.md#unexpectederror) - [UnsupportedBrowser](Frontend_Utils_BrowserChecks.Incompatibility.md#unsupportedbrowser) ## Enumeration members ### MobileOrTablet • **MobileOrTablet** = `"mobile_or_tablet"` --- ### NoIDB • **NoIDB** = `"no_idb"` --- ### NotLoggedInOrEnabled • **NotLoggedInOrEnabled** = `"not_logged_in_or_enabled"` --- ### NotRopsten • **NotRopsten** = `"not_ropsten"` --- ### UnexpectedError • **UnexpectedError** = `"unexpected_error"` --- ### UnsupportedBrowser • **UnsupportedBrowser** = `"unsupported_browser"` ================================================ FILE: docs/enums/Frontend_Utils_TerminalTypes.TerminalTextStyle.md ================================================ # Enumeration: TerminalTextStyle [Frontend/Utils/TerminalTypes](../modules/Frontend_Utils_TerminalTypes.md).TerminalTextStyle ## Table of contents ### Enumeration members - [Blue](Frontend_Utils_TerminalTypes.TerminalTextStyle.md#blue) - [Green](Frontend_Utils_TerminalTypes.TerminalTextStyle.md#green) - [Invisible](Frontend_Utils_TerminalTypes.TerminalTextStyle.md#invisible) - [Mythic](Frontend_Utils_TerminalTypes.TerminalTextStyle.md#mythic) - [Red](Frontend_Utils_TerminalTypes.TerminalTextStyle.md#red) - [Sub](Frontend_Utils_TerminalTypes.TerminalTextStyle.md#sub) - [Subber](Frontend_Utils_TerminalTypes.TerminalTextStyle.md#subber) - [Text](Frontend_Utils_TerminalTypes.TerminalTextStyle.md#text) - [Underline](Frontend_Utils_TerminalTypes.TerminalTextStyle.md#underline) - [White](Frontend_Utils_TerminalTypes.TerminalTextStyle.md#white) ## Enumeration members ### Blue • **Blue** = `6` --- ### Green • **Green** = `0` --- ### Invisible • **Invisible** = `7` --- ### Mythic • **Mythic** = `9` --- ### Red • **Red** = `5` --- ### Sub • **Sub** = `1` --- ### Subber • **Subber** = `2` --- ### Text • **Text** = `3` --- ### Underline • **Underline** = `8` --- ### White • **White** = `4` ================================================ FILE: docs/enums/Frontend_Utils_UIEmitter.UIEmitterEvent.md ================================================ # Enumeration: UIEmitterEvent [Frontend/Utils/UIEmitter](../modules/Frontend_Utils_UIEmitter.md).UIEmitterEvent ## Table of contents ### Enumeration members - [CanvasMouseDown](Frontend_Utils_UIEmitter.UIEmitterEvent.md#canvasmousedown) - [CanvasMouseMove](Frontend_Utils_UIEmitter.UIEmitterEvent.md#canvasmousemove) - [CanvasMouseOut](Frontend_Utils_UIEmitter.UIEmitterEvent.md#canvasmouseout) - [CanvasMouseUp](Frontend_Utils_UIEmitter.UIEmitterEvent.md#canvasmouseup) - [CanvasScroll](Frontend_Utils_UIEmitter.UIEmitterEvent.md#canvasscroll) - [CenterPlanet](Frontend_Utils_UIEmitter.UIEmitterEvent.md#centerplanet) - [DepositArtifact](Frontend_Utils_UIEmitter.UIEmitterEvent.md#depositartifact) - [DepositToPlanet](Frontend_Utils_UIEmitter.UIEmitterEvent.md#deposittoplanet) - [GamePlanetSelected](Frontend_Utils_UIEmitter.UIEmitterEvent.md#gameplanetselected) - [SelectArtifact](Frontend_Utils_UIEmitter.UIEmitterEvent.md#selectartifact) - [SendCancelled](Frontend_Utils_UIEmitter.UIEmitterEvent.md#sendcancelled) - [SendCompleted](Frontend_Utils_UIEmitter.UIEmitterEvent.md#sendcompleted) - [SendInitiated](Frontend_Utils_UIEmitter.UIEmitterEvent.md#sendinitiated) - [ShowArtifact](Frontend_Utils_UIEmitter.UIEmitterEvent.md#showartifact) - [UIChange](Frontend_Utils_UIEmitter.UIEmitterEvent.md#uichange) - [WindowResize](Frontend_Utils_UIEmitter.UIEmitterEvent.md#windowresize) - [WorldMouseClick](Frontend_Utils_UIEmitter.UIEmitterEvent.md#worldmouseclick) - [WorldMouseDown](Frontend_Utils_UIEmitter.UIEmitterEvent.md#worldmousedown) - [WorldMouseMove](Frontend_Utils_UIEmitter.UIEmitterEvent.md#worldmousemove) - [WorldMouseOut](Frontend_Utils_UIEmitter.UIEmitterEvent.md#worldmouseout) - [WorldMouseUp](Frontend_Utils_UIEmitter.UIEmitterEvent.md#worldmouseup) - [ZoomIn](Frontend_Utils_UIEmitter.UIEmitterEvent.md#zoomin) - [ZoomOut](Frontend_Utils_UIEmitter.UIEmitterEvent.md#zoomout) ## Enumeration members ### CanvasMouseDown • **CanvasMouseDown** = `"CanvasMouseDown"` --- ### CanvasMouseMove • **CanvasMouseMove** = `"CanvasMouseMove"` --- ### CanvasMouseOut • **CanvasMouseOut** = `"CanvasMouseOut"` --- ### CanvasMouseUp • **CanvasMouseUp** = `"CanvasMouseUp"` --- ### CanvasScroll • **CanvasScroll** = `"CanvasScroll"` --- ### CenterPlanet • **CenterPlanet** = `"CenterPlanet"` --- ### DepositArtifact • **DepositArtifact** = `"DepositArtifact"` --- ### DepositToPlanet • **DepositToPlanet** = `"DepositToPlanet"` --- ### GamePlanetSelected • **GamePlanetSelected** = `"GamePlanetSelected"` --- ### SelectArtifact • **SelectArtifact** = `"SelectArtifact"` --- ### SendCancelled • **SendCancelled** = `"SendCancelled"` --- ### SendCompleted • **SendCompleted** = `"SendCompleted"` --- ### SendInitiated • **SendInitiated** = `"SendInitiated"` --- ### ShowArtifact • **ShowArtifact** = `"ShowArtifact"` --- ### UIChange • **UIChange** = `"UIChange"` --- ### WindowResize • **WindowResize** = `"WindowResize"` --- ### WorldMouseClick • **WorldMouseClick** = `"WorldMouseClick"` --- ### WorldMouseDown • **WorldMouseDown** = `"WorldMouseDown"` --- ### WorldMouseMove • **WorldMouseMove** = `"WorldMouseMove"` --- ### WorldMouseOut • **WorldMouseOut** = `"WorldMouseOut"` --- ### WorldMouseUp • **WorldMouseUp** = `"WorldMouseUp"` --- ### ZoomIn • **ZoomIn** = `"ZoomIn"` --- ### ZoomOut • **ZoomOut** = `"ZoomOut"` ================================================ FILE: docs/enums/Frontend_Utils_constants.DFZIndex.md ================================================ # Enumeration: DFZIndex [Frontend/Utils/constants](../modules/Frontend_Utils_constants.md).DFZIndex ## Table of contents ### Enumeration members - [HoverPlanet](Frontend_Utils_constants.DFZIndex.md#hoverplanet) - [MenuBar](Frontend_Utils_constants.DFZIndex.md#menubar) - [Modal](Frontend_Utils_constants.DFZIndex.md#modal) - [Notification](Frontend_Utils_constants.DFZIndex.md#notification) - [Tooltip](Frontend_Utils_constants.DFZIndex.md#tooltip) ## Enumeration members ### HoverPlanet • **HoverPlanet** = `1001` --- ### MenuBar • **MenuBar** = `4` --- ### Modal • **Modal** = `1001` --- ### Notification • **Notification** = `1000` --- ### Tooltip • **Tooltip** = `16000000` ================================================ FILE: docs/enums/Frontend_Views_PlanetNotifications.PlanetNotifType.md ================================================ # Enumeration: PlanetNotifType [Frontend/Views/PlanetNotifications](../modules/Frontend_Views_PlanetNotifications.md).PlanetNotifType ## Table of contents ### Enumeration members - [CanAddEmoji](Frontend_Views_PlanetNotifications.PlanetNotifType.md#canaddemoji) - [Claimed](Frontend_Views_PlanetNotifications.PlanetNotifType.md#claimed) - [DistanceFromCenter](Frontend_Views_PlanetNotifications.PlanetNotifType.md#distancefromcenter) - [PlanetCanUpgrade](Frontend_Views_PlanetNotifications.PlanetNotifType.md#planetcanupgrade) ## Enumeration members ### CanAddEmoji • **CanAddEmoji** = `3` --- ### Claimed • **Claimed** = `1` --- ### DistanceFromCenter • **DistanceFromCenter** = `2` --- ### PlanetCanUpgrade • **PlanetCanUpgrade** = `0` ================================================ FILE: docs/enums/types_darkforest_api_ContractsAPITypes.ContractEvent.md ================================================ # Enumeration: ContractEvent [\_types/darkforest/api/ContractsAPITypes](../modules/types_darkforest_api_ContractsAPITypes.md).ContractEvent ## Table of contents ### Enumeration members - [AdminGiveSpaceship](types_darkforest_api_ContractsAPITypes.ContractEvent.md#admingivespaceship) - [AdminOwnershipChanged](types_darkforest_api_ContractsAPITypes.ContractEvent.md#adminownershipchanged) - [ArrivalQueued](types_darkforest_api_ContractsAPITypes.ContractEvent.md#arrivalqueued) - [ArtifactActivated](types_darkforest_api_ContractsAPITypes.ContractEvent.md#artifactactivated) - [ArtifactDeactivated](types_darkforest_api_ContractsAPITypes.ContractEvent.md#artifactdeactivated) - [ArtifactDeposited](types_darkforest_api_ContractsAPITypes.ContractEvent.md#artifactdeposited) - [ArtifactFound](types_darkforest_api_ContractsAPITypes.ContractEvent.md#artifactfound) - [ArtifactWithdrawn](types_darkforest_api_ContractsAPITypes.ContractEvent.md#artifactwithdrawn) - [LobbyCreated](types_darkforest_api_ContractsAPITypes.ContractEvent.md#lobbycreated) - [LocationRevealed](types_darkforest_api_ContractsAPITypes.ContractEvent.md#locationrevealed) - [PauseStateChanged](types_darkforest_api_ContractsAPITypes.ContractEvent.md#pausestatechanged) - [PlanetCaptured](types_darkforest_api_ContractsAPITypes.ContractEvent.md#planetcaptured) - [PlanetHatBought](types_darkforest_api_ContractsAPITypes.ContractEvent.md#planethatbought) - [PlanetInvaded](types_darkforest_api_ContractsAPITypes.ContractEvent.md#planetinvaded) - [PlanetSilverWithdrawn](types_darkforest_api_ContractsAPITypes.ContractEvent.md#planetsilverwithdrawn) - [PlanetTransferred](types_darkforest_api_ContractsAPITypes.ContractEvent.md#planettransferred) - [PlanetUpgraded](types_darkforest_api_ContractsAPITypes.ContractEvent.md#planetupgraded) - [PlayerInitialized](types_darkforest_api_ContractsAPITypes.ContractEvent.md#playerinitialized) ## Enumeration members ### AdminGiveSpaceship • **AdminGiveSpaceship** = `"AdminGiveSpaceship"` --- ### AdminOwnershipChanged • **AdminOwnershipChanged** = `"AdminOwnershipChanged"` --- ### ArrivalQueued • **ArrivalQueued** = `"ArrivalQueued"` --- ### ArtifactActivated • **ArtifactActivated** = `"ArtifactActivated"` --- ### ArtifactDeactivated • **ArtifactDeactivated** = `"ArtifactDeactivated"` --- ### ArtifactDeposited • **ArtifactDeposited** = `"ArtifactDeposited"` --- ### ArtifactFound • **ArtifactFound** = `"ArtifactFound"` --- ### ArtifactWithdrawn • **ArtifactWithdrawn** = `"ArtifactWithdrawn"` --- ### LobbyCreated • **LobbyCreated** = `"LobbyCreated"` --- ### LocationRevealed • **LocationRevealed** = `"LocationRevealed"` --- ### PauseStateChanged • **PauseStateChanged** = `"PauseStateChanged"` --- ### PlanetCaptured • **PlanetCaptured** = `"PlanetCaptured"` --- ### PlanetHatBought • **PlanetHatBought** = `"PlanetHatBought"` --- ### PlanetInvaded • **PlanetInvaded** = `"PlanetInvaded"` --- ### PlanetSilverWithdrawn • **PlanetSilverWithdrawn** = `"PlanetSilverWithdrawn"` --- ### PlanetTransferred • **PlanetTransferred** = `"PlanetTransferred"` --- ### PlanetUpgraded • **PlanetUpgraded** = `"PlanetUpgraded"` --- ### PlayerInitialized • **PlayerInitialized** = `"PlayerInitialized"` ================================================ FILE: docs/enums/types_darkforest_api_ContractsAPITypes.ContractsAPIEvent.md ================================================ # Enumeration: ContractsAPIEvent [\_types/darkforest/api/ContractsAPITypes](../modules/types_darkforest_api_ContractsAPITypes.md).ContractsAPIEvent ## Table of contents ### Enumeration members - [ArrivalQueued](types_darkforest_api_ContractsAPITypes.ContractsAPIEvent.md#arrivalqueued) - [ArtifactUpdate](types_darkforest_api_ContractsAPITypes.ContractsAPIEvent.md#artifactupdate) - [LobbyCreated](types_darkforest_api_ContractsAPITypes.ContractsAPIEvent.md#lobbycreated) - [LocationRevealed](types_darkforest_api_ContractsAPITypes.ContractsAPIEvent.md#locationrevealed) - [PauseStateChanged](types_darkforest_api_ContractsAPITypes.ContractsAPIEvent.md#pausestatechanged) - [PlanetClaimed](types_darkforest_api_ContractsAPITypes.ContractsAPIEvent.md#planetclaimed) - [PlanetTransferred](types_darkforest_api_ContractsAPITypes.ContractsAPIEvent.md#planettransferred) - [PlanetUpdate](types_darkforest_api_ContractsAPITypes.ContractsAPIEvent.md#planetupdate) - [PlayerUpdate](types_darkforest_api_ContractsAPITypes.ContractsAPIEvent.md#playerupdate) - [RadiusUpdated](types_darkforest_api_ContractsAPITypes.ContractsAPIEvent.md#radiusupdated) - [TxCancelled](types_darkforest_api_ContractsAPITypes.ContractsAPIEvent.md#txcancelled) - [TxConfirmed](types_darkforest_api_ContractsAPITypes.ContractsAPIEvent.md#txconfirmed) - [TxErrored](types_darkforest_api_ContractsAPITypes.ContractsAPIEvent.md#txerrored) - [TxPrioritized](types_darkforest_api_ContractsAPITypes.ContractsAPIEvent.md#txprioritized) - [TxProcessing](types_darkforest_api_ContractsAPITypes.ContractsAPIEvent.md#txprocessing) - [TxQueued](types_darkforest_api_ContractsAPITypes.ContractsAPIEvent.md#txqueued) - [TxSubmitted](types_darkforest_api_ContractsAPITypes.ContractsAPIEvent.md#txsubmitted) ## Enumeration members ### ArrivalQueued • **ArrivalQueued** = `"ArrivalQueued"` --- ### ArtifactUpdate • **ArtifactUpdate** = `"ArtifactUpdate"` --- ### LobbyCreated • **LobbyCreated** = `"LobbyCreated"` --- ### LocationRevealed • **LocationRevealed** = `"LocationRevealed"` --- ### PauseStateChanged • **PauseStateChanged** = `"PauseStateChanged"` --- ### PlanetClaimed • **PlanetClaimed** = `"PlanetClaimed"` --- ### PlanetTransferred • **PlanetTransferred** = `"PlanetTransferred"` --- ### PlanetUpdate • **PlanetUpdate** = `"PlanetUpdate"` --- ### PlayerUpdate • **PlayerUpdate** = `"PlayerUpdate"` --- ### RadiusUpdated • **RadiusUpdated** = `"RadiusUpdated"` --- ### TxCancelled • **TxCancelled** = `"TxCancelled"` The transaction was cancelled before it left the queue. --- ### TxConfirmed • **TxConfirmed** = `"TxConfirmed"` The transaction has been confirmed. --- ### TxErrored • **TxErrored** = `"TxErrored"` The transaction has failed for some reason. This could either be a revert or a purely client side error. In the case of a revert, the transaction hash will be included in the transaction object. --- ### TxPrioritized • **TxPrioritized** = `"TxPrioritized"` The transaction is queued, but is prioritized for execution above other queued transactions. --- ### TxProcessing • **TxProcessing** = `"TxProcessing"` The transaction has been removed from the queue and is calculating arguments in preparation for submission. --- ### TxQueued • **TxQueued** = `"TxQueued"` The transaction has been queued for future execution. --- ### TxSubmitted • **TxSubmitted** = `"TxSubmitted"` The transaction has been submitted and we are awaiting confirmation. ================================================ FILE: docs/enums/types_darkforest_api_ContractsAPITypes.InitArgIdxs.md ================================================ # Enumeration: InitArgIdxs [\_types/darkforest/api/ContractsAPITypes](../modules/types_darkforest_api_ContractsAPITypes.md).InitArgIdxs ## Table of contents ### Enumeration members - [LOCATION_ID](types_darkforest_api_ContractsAPITypes.InitArgIdxs.md#location_id) - [PERLIN](types_darkforest_api_ContractsAPITypes.InitArgIdxs.md#perlin) - [PERLIN_LENGTH_SCALE](types_darkforest_api_ContractsAPITypes.InitArgIdxs.md#perlin_length_scale) - [PERLIN_MIRROR_X](types_darkforest_api_ContractsAPITypes.InitArgIdxs.md#perlin_mirror_x) - [PERLIN_MIRROR_Y](types_darkforest_api_ContractsAPITypes.InitArgIdxs.md#perlin_mirror_y) - [PLANETHASH_KEY](types_darkforest_api_ContractsAPITypes.InitArgIdxs.md#planethash_key) - [RADIUS](types_darkforest_api_ContractsAPITypes.InitArgIdxs.md#radius) - [SPACETYPE_KEY](types_darkforest_api_ContractsAPITypes.InitArgIdxs.md#spacetype_key) ## Enumeration members ### LOCATION_ID • **LOCATION_ID** = `0` --- ### PERLIN • **PERLIN** = `1` --- ### PERLIN_LENGTH_SCALE • **PERLIN_LENGTH_SCALE** = `5` --- ### PERLIN_MIRROR_X • **PERLIN_MIRROR_X** = `6` --- ### PERLIN_MIRROR_Y • **PERLIN_MIRROR_Y** = `7` --- ### PLANETHASH_KEY • **PLANETHASH_KEY** = `3` --- ### RADIUS • **RADIUS** = `2` --- ### SPACETYPE_KEY • **SPACETYPE_KEY** = `4` ================================================ FILE: docs/enums/types_darkforest_api_ContractsAPITypes.MoveArgIdxs.md ================================================ # Enumeration: MoveArgIdxs [\_types/darkforest/api/ContractsAPITypes](../modules/types_darkforest_api_ContractsAPITypes.md).MoveArgIdxs ## Table of contents ### Enumeration members - [ARTIFACT_SENT](types_darkforest_api_ContractsAPITypes.MoveArgIdxs.md#artifact_sent) - [DIST_MAX](types_darkforest_api_ContractsAPITypes.MoveArgIdxs.md#dist_max) - [FROM_ID](types_darkforest_api_ContractsAPITypes.MoveArgIdxs.md#from_id) - [PERLIN_LENGTH_SCALE](types_darkforest_api_ContractsAPITypes.MoveArgIdxs.md#perlin_length_scale) - [PERLIN_MIRROR_X](types_darkforest_api_ContractsAPITypes.MoveArgIdxs.md#perlin_mirror_x) - [PERLIN_MIRROR_Y](types_darkforest_api_ContractsAPITypes.MoveArgIdxs.md#perlin_mirror_y) - [PLANETHASH_KEY](types_darkforest_api_ContractsAPITypes.MoveArgIdxs.md#planethash_key) - [SHIPS_SENT](types_darkforest_api_ContractsAPITypes.MoveArgIdxs.md#ships_sent) - [SILVER_SENT](types_darkforest_api_ContractsAPITypes.MoveArgIdxs.md#silver_sent) - [SPACETYPE_KEY](types_darkforest_api_ContractsAPITypes.MoveArgIdxs.md#spacetype_key) - [TO_ID](types_darkforest_api_ContractsAPITypes.MoveArgIdxs.md#to_id) - [TO_PERLIN](types_darkforest_api_ContractsAPITypes.MoveArgIdxs.md#to_perlin) - [TO_RADIUS](types_darkforest_api_ContractsAPITypes.MoveArgIdxs.md#to_radius) ## Enumeration members ### ARTIFACT_SENT • **ARTIFACT_SENT** = `12` --- ### DIST_MAX • **DIST_MAX** = `4` --- ### FROM_ID • **FROM_ID** = `0` --- ### PERLIN_LENGTH_SCALE • **PERLIN_LENGTH_SCALE** = `7` --- ### PERLIN_MIRROR_X • **PERLIN_MIRROR_X** = `8` --- ### PERLIN_MIRROR_Y • **PERLIN_MIRROR_Y** = `9` --- ### PLANETHASH_KEY • **PLANETHASH_KEY** = `5` --- ### SHIPS_SENT • **SHIPS_SENT** = `10` --- ### SILVER_SENT • **SILVER_SENT** = `11` --- ### SPACETYPE_KEY • **SPACETYPE_KEY** = `6` --- ### TO_ID • **TO_ID** = `1` --- ### TO_PERLIN • **TO_PERLIN** = `2` --- ### TO_RADIUS • **TO_RADIUS** = `3` ================================================ FILE: docs/enums/types_darkforest_api_ContractsAPITypes.PlanetEventType.md ================================================ # Enumeration: PlanetEventType [\_types/darkforest/api/ContractsAPITypes](../modules/types_darkforest_api_ContractsAPITypes.md).PlanetEventType ## Table of contents ### Enumeration members - [ARRIVAL](types_darkforest_api_ContractsAPITypes.PlanetEventType.md#arrival) ## Enumeration members ### ARRIVAL • **ARRIVAL** = `0` ================================================ FILE: docs/enums/types_darkforest_api_ContractsAPITypes.UpgradeArgIdxs.md ================================================ # Enumeration: UpgradeArgIdxs [\_types/darkforest/api/ContractsAPITypes](../modules/types_darkforest_api_ContractsAPITypes.md).UpgradeArgIdxs ## Table of contents ### Enumeration members - [LOCATION_ID](types_darkforest_api_ContractsAPITypes.UpgradeArgIdxs.md#location_id) - [UPGRADE_BRANCH](types_darkforest_api_ContractsAPITypes.UpgradeArgIdxs.md#upgrade_branch) ## Enumeration members ### LOCATION_ID • **LOCATION_ID** = `0` --- ### UPGRADE_BRANCH • **UPGRADE_BRANCH** = `1` ================================================ FILE: docs/enums/types_darkforest_api_ContractsAPITypes.ZKArgIdx.md ================================================ # Enumeration: ZKArgIdx [\_types/darkforest/api/ContractsAPITypes](../modules/types_darkforest_api_ContractsAPITypes.md).ZKArgIdx ## Table of contents ### Enumeration members - [DATA](types_darkforest_api_ContractsAPITypes.ZKArgIdx.md#data) - [PROOF_A](types_darkforest_api_ContractsAPITypes.ZKArgIdx.md#proof_a) - [PROOF_B](types_darkforest_api_ContractsAPITypes.ZKArgIdx.md#proof_b) - [PROOF_C](types_darkforest_api_ContractsAPITypes.ZKArgIdx.md#proof_c) ## Enumeration members ### DATA • **DATA** = `3` --- ### PROOF_A • **PROOF_A** = `0` --- ### PROOF_B • **PROOF_B** = `1` --- ### PROOF_C • **PROOF_C** = `2` ================================================ FILE: docs/enums/types_global_GlobalTypes.StatIdx.md ================================================ # Enumeration: StatIdx [\_types/global/GlobalTypes](../modules/types_global_GlobalTypes.md).StatIdx ## Table of contents ### Enumeration members - [Defense](types_global_GlobalTypes.StatIdx.md#defense) - [EnergyCap](types_global_GlobalTypes.StatIdx.md#energycap) - [EnergyGro](types_global_GlobalTypes.StatIdx.md#energygro) - [Range](types_global_GlobalTypes.StatIdx.md#range) - [SpaceJunk](types_global_GlobalTypes.StatIdx.md#spacejunk) - [Speed](types_global_GlobalTypes.StatIdx.md#speed) ## Enumeration members ### Defense • **Defense** = `4` --- ### EnergyCap • **EnergyCap** = `0` --- ### EnergyGro • **EnergyGro** = `1` --- ### Range • **Range** = `2` --- ### SpaceJunk • **SpaceJunk** = `5` --- ### Speed • **Speed** = `3` ================================================ FILE: docs/interfaces/Backend_GameLogic_ArrivalUtils.PlanetDiff.md ================================================ # Interface: PlanetDiff [Backend/GameLogic/ArrivalUtils](../modules/Backend_GameLogic_ArrivalUtils.md).PlanetDiff **`param`** The previously calculated state of a planet **`param`** The current calculated state of the planet **`param`** The Arrival that caused the state change ## Table of contents ### Properties - [arrival](Backend_GameLogic_ArrivalUtils.PlanetDiff.md#arrival) - [current](Backend_GameLogic_ArrivalUtils.PlanetDiff.md#current) - [previous](Backend_GameLogic_ArrivalUtils.PlanetDiff.md#previous) ## Properties ### arrival • **arrival**: `QueuedArrival` --- ### current • **current**: `Planet` --- ### previous • **previous**: `Planet` ================================================ FILE: docs/interfaces/Backend_GameLogic_InitialGameStateDownloader.InitialGameState.md ================================================ # Interface: InitialGameState [Backend/GameLogic/InitialGameStateDownloader](../modules/Backend_GameLogic_InitialGameStateDownloader.md).InitialGameState ## Table of contents ### Properties - [allClaimedCoords](Backend_GameLogic_InitialGameStateDownloader.InitialGameState.md#allclaimedcoords) - [allRevealedCoords](Backend_GameLogic_InitialGameStateDownloader.InitialGameState.md#allrevealedcoords) - [allTouchedPlanetIds](Backend_GameLogic_InitialGameStateDownloader.InitialGameState.md#alltouchedplanetids) - [arrivals](Backend_GameLogic_InitialGameStateDownloader.InitialGameState.md#arrivals) - [artifactsOnVoyages](Backend_GameLogic_InitialGameStateDownloader.InitialGameState.md#artifactsonvoyages) - [claimedCoordsMap](Backend_GameLogic_InitialGameStateDownloader.InitialGameState.md#claimedcoordsmap) - [contractConstants](Backend_GameLogic_InitialGameStateDownloader.InitialGameState.md#contractconstants) - [heldArtifacts](Backend_GameLogic_InitialGameStateDownloader.InitialGameState.md#heldartifacts) - [loadedPlanets](Backend_GameLogic_InitialGameStateDownloader.InitialGameState.md#loadedplanets) - [myArtifacts](Backend_GameLogic_InitialGameStateDownloader.InitialGameState.md#myartifacts) - [paused](Backend_GameLogic_InitialGameStateDownloader.InitialGameState.md#paused) - [pendingMoves](Backend_GameLogic_InitialGameStateDownloader.InitialGameState.md#pendingmoves) - [planetVoyageIdMap](Backend_GameLogic_InitialGameStateDownloader.InitialGameState.md#planetvoyageidmap) - [players](Backend_GameLogic_InitialGameStateDownloader.InitialGameState.md#players) - [revealedCoordsMap](Backend_GameLogic_InitialGameStateDownloader.InitialGameState.md#revealedcoordsmap) - [touchedAndLocatedPlanets](Backend_GameLogic_InitialGameStateDownloader.InitialGameState.md#touchedandlocatedplanets) - [twitters](Backend_GameLogic_InitialGameStateDownloader.InitialGameState.md#twitters) - [worldRadius](Backend_GameLogic_InitialGameStateDownloader.InitialGameState.md#worldradius) ## Properties ### allClaimedCoords • `Optional` **allClaimedCoords**: `ClaimedCoords`[] --- ### allRevealedCoords • **allRevealedCoords**: `RevealedCoords`[] --- ### allTouchedPlanetIds • **allTouchedPlanetIds**: `LocationId`[] --- ### arrivals • **arrivals**: `Map`<`VoyageId`, `QueuedArrival`\> --- ### artifactsOnVoyages • **artifactsOnVoyages**: `Artifact`[] --- ### claimedCoordsMap • `Optional` **claimedCoordsMap**: `Map`<`LocationId`, `ClaimedCoords`\> --- ### contractConstants • **contractConstants**: [`ContractConstants`](types_darkforest_api_ContractsAPITypes.ContractConstants.md) --- ### heldArtifacts • **heldArtifacts**: `Artifact`[][] --- ### loadedPlanets • **loadedPlanets**: `LocationId`[] --- ### myArtifacts • **myArtifacts**: `Artifact`[] --- ### paused • **paused**: `boolean` --- ### pendingMoves • **pendingMoves**: `QueuedArrival`[] --- ### planetVoyageIdMap • **planetVoyageIdMap**: `Map`<`LocationId`, `VoyageId`[]\> --- ### players • **players**: `Map`<`string`, `Player`\> --- ### revealedCoordsMap • **revealedCoordsMap**: `Map`<`LocationId`, `RevealedCoords`\> --- ### touchedAndLocatedPlanets • **touchedAndLocatedPlanets**: `Map`<`LocationId`, `Planet`\> --- ### twitters • **twitters**: [`AddressTwitterMap`](../modules/types_darkforest_api_UtilityServerAPITypes.md#addresstwittermap) --- ### worldRadius • **worldRadius**: `number` ================================================ FILE: docs/interfaces/Backend_Miner_MiningPatterns.MiningPattern.md ================================================ # Interface: MiningPattern [Backend/Miner/MiningPatterns](../modules/Backend_Miner_MiningPatterns.md).MiningPattern ## Implemented by - [`SpiralPattern`](../classes/Backend_Miner_MiningPatterns.SpiralPattern.md) - [`SwissCheesePattern`](../classes/Backend_Miner_MiningPatterns.SwissCheesePattern.md) - [`TowardsCenterPattern`](../classes/Backend_Miner_MiningPatterns.TowardsCenterPattern.md) - [`TowardsCenterPatternV2`](../classes/Backend_Miner_MiningPatterns.TowardsCenterPatternV2.md) ## Table of contents ### Properties - [fromChunk](Backend_Miner_MiningPatterns.MiningPattern.md#fromchunk) - [type](Backend_Miner_MiningPatterns.MiningPattern.md#type) ### Methods - [nextChunk](Backend_Miner_MiningPatterns.MiningPattern.md#nextchunk) ## Properties ### fromChunk • **fromChunk**: `Rectangle` --- ### type • **type**: [`MiningPatternType`](../enums/Backend_Miner_MiningPatterns.MiningPatternType.md) ## Methods ### nextChunk ▸ **nextChunk**(`prevLoc`): `Rectangle` #### Parameters | Name | Type | | :-------- | :---------- | | `prevLoc` | `Rectangle` | #### Returns `Rectangle` ================================================ FILE: docs/interfaces/Backend_Network_AccountManager.Account.md ================================================ # Interface: Account [Backend/Network/AccountManager](../modules/Backend_Network_AccountManager.md).Account Represents an account with which the user plays the game. ## Table of contents ### Properties - [address](Backend_Network_AccountManager.Account.md#address) - [privateKey](Backend_Network_AccountManager.Account.md#privatekey) ## Properties ### address • **address**: `EthAddress` --- ### privateKey • **privateKey**: `string` ================================================ FILE: docs/interfaces/Backend_Plugins_EmbeddedPluginLoader.EmbeddedPlugin.md ================================================ # Interface: EmbeddedPlugin [Backend/Plugins/EmbeddedPluginLoader](../modules/Backend_Plugins_EmbeddedPluginLoader.md).EmbeddedPlugin This interface represents an embedded plugin, which is stored in `embedded_plugins/`. ## Table of contents ### Properties - [code](Backend_Plugins_EmbeddedPluginLoader.EmbeddedPlugin.md#code) - [id](Backend_Plugins_EmbeddedPluginLoader.EmbeddedPlugin.md#id) - [name](Backend_Plugins_EmbeddedPluginLoader.EmbeddedPlugin.md#name) ## Properties ### code • **code**: `string` --- ### id • **id**: `PluginId` --- ### name • **name**: `string` ================================================ FILE: docs/interfaces/Backend_Plugins_PluginProcess.PluginProcess.md ================================================ # Interface: PluginProcess [Backend/Plugins/PluginProcess](../modules/Backend_Plugins_PluginProcess.md).PluginProcess All plugins must conform to this interface. Provides facilities for displaying an interactive UI, as well as references to game state, which are set externally. ## Table of contents ### Constructors - [constructor](Backend_Plugins_PluginProcess.PluginProcess.md#constructor) ### Methods - [destroy](Backend_Plugins_PluginProcess.PluginProcess.md#destroy) - [draw](Backend_Plugins_PluginProcess.PluginProcess.md#draw) - [render](Backend_Plugins_PluginProcess.PluginProcess.md#render) ## Constructors ### constructor • **new PluginProcess**() ## Methods ### destroy ▸ `Optional` **destroy**(): `void` Called when the plugin is unloaded. Plugins unload whenever the plugin is edited (modified and saved, or deleted). #### Returns `void` --- ### draw ▸ `Optional` **draw**(`ctx`): `void` If present, called at the same framerate the the game is running at, and allows you to draw on top of the game UI. #### Parameters | Name | Type | | :---- | :------------------------- | | `ctx` | `CanvasRenderingContext2D` | #### Returns `void` --- ### render ▸ `Optional` **render**(`into`): `void` If present, called once when the user clicks 'run' in the plugin manager modal. #### Parameters | Name | Type | | :----- | :--------------- | | `into` | `HTMLDivElement` | #### Returns `void` ================================================ FILE: docs/interfaces/Backend_Plugins_SerializedPlugin.SerializedPlugin.md ================================================ # Interface: SerializedPlugin [Backend/Plugins/SerializedPlugin](../modules/Backend_Plugins_SerializedPlugin.md).SerializedPlugin Represents a plugin that the user has added to their game. Used internally for storing plugins. Not used for evaluating plugins! ## Table of contents ### Properties - [code](Backend_Plugins_SerializedPlugin.SerializedPlugin.md#code) - [id](Backend_Plugins_SerializedPlugin.SerializedPlugin.md#id) - [lastEdited](Backend_Plugins_SerializedPlugin.SerializedPlugin.md#lastedited) - [name](Backend_Plugins_SerializedPlugin.SerializedPlugin.md#name) ## Properties ### code • **code**: `string` This code is a javascript object that complies with the [PluginProcess](Backend_Plugins_PluginProcess.PluginProcess.md) interface. --- ### id • **id**: `PluginId` Unique ID, assigned at the time the plugin is first saved. --- ### lastEdited • **lastEdited**: `number` {@code new Date.getTime()} at the point that this plugin was saved --- ### name • **name**: `string` Shown in the list of plugins. ================================================ FILE: docs/interfaces/Frontend_Components_TextLoadingBar.LoadingBarHandle.md ================================================ # Interface: LoadingBarHandle [Frontend/Components/TextLoadingBar](../modules/Frontend_Components_TextLoadingBar.md).LoadingBarHandle ## Table of contents ### Methods - [setFractionCompleted](Frontend_Components_TextLoadingBar.LoadingBarHandle.md#setfractioncompleted) ## Methods ### setFractionCompleted ▸ **setFractionCompleted**(`fractionCompleted`): `void` #### Parameters | Name | Type | | :------------------ | :------- | | `fractionCompleted` | `number` | #### Returns `void` ================================================ FILE: docs/interfaces/Frontend_Panes_Lobbies_LobbiesUtils.LobbiesPaneProps.md ================================================ # Interface: LobbiesPaneProps [Frontend/Panes/Lobbies/LobbiesUtils](../modules/Frontend_Panes_Lobbies_LobbiesUtils.md).LobbiesPaneProps ## Table of contents ### Properties - [config](Frontend_Panes_Lobbies_LobbiesUtils.LobbiesPaneProps.md#config) ### Methods - [onUpdate](Frontend_Panes_Lobbies_LobbiesUtils.LobbiesPaneProps.md#onupdate) ## Properties ### config • **config**: [`LobbyConfigState`](../modules/Frontend_Panes_Lobbies_Reducer.md#lobbyconfigstate) ## Methods ### onUpdate ▸ **onUpdate**(`change`): `void` #### Parameters | Name | Type | | :------- | :------------------------------------------------------------------------------------ | | `change` | [`LobbyConfigAction`](../modules/Frontend_Panes_Lobbies_Reducer.md#lobbyconfigaction) | #### Returns `void` ================================================ FILE: docs/interfaces/Frontend_Panes_Tooltip.TooltipProps.md ================================================ # Interface: TooltipProps [Frontend/Panes/Tooltip](../modules/Frontend_Panes_Tooltip.md).TooltipProps ## Hierarchy - [`TooltipTriggerProps`](Frontend_Panes_Tooltip.TooltipTriggerProps.md) ↳ **`TooltipProps`** ## Table of contents ### Properties - [children](Frontend_Panes_Tooltip.TooltipProps.md#children) - [extraContent](Frontend_Panes_Tooltip.TooltipProps.md#extracontent) - [left](Frontend_Panes_Tooltip.TooltipProps.md#left) - [name](Frontend_Panes_Tooltip.TooltipProps.md#name) - [style](Frontend_Panes_Tooltip.TooltipProps.md#style) - [top](Frontend_Panes_Tooltip.TooltipProps.md#top) ## Properties ### children • **children**: `ReactNode` A [TooltipTrigger](../modules/Frontend_Panes_Tooltip.md#tooltiptrigger) wraps this child, and causes a tooltip to appear when the user hovers over it. #### Inherited from [TooltipTriggerProps](Frontend_Panes_Tooltip.TooltipTriggerProps.md).[children](Frontend_Panes_Tooltip.TooltipTriggerProps.md#children) --- ### extraContent • `Optional` **extraContent**: `ReactNode` You can append some dynamic content to the given tooltip by setting this field to a React node. #### Inherited from [TooltipTriggerProps](Frontend_Panes_Tooltip.TooltipTriggerProps.md).[extraContent](Frontend_Panes_Tooltip.TooltipTriggerProps.md#extracontent) --- ### left • **left**: `number` --- ### name • **name**: `undefined` \| `TooltipName` The name of the tooltip element to display. You can see all the concrete tooltip contents in the file called {@link TooltipPanes}. Set to `undefined` to not render the tooltip. #### Inherited from [TooltipTriggerProps](Frontend_Panes_Tooltip.TooltipTriggerProps.md).[name](Frontend_Panes_Tooltip.TooltipTriggerProps.md#name) --- ### style • `Optional` **style**: `CSSProperties` You can optionally style the tooltip trigger element, not the tooltip itself. #### Inherited from [TooltipTriggerProps](Frontend_Panes_Tooltip.TooltipTriggerProps.md).[style](Frontend_Panes_Tooltip.TooltipTriggerProps.md#style) --- ### top • **top**: `number` ================================================ FILE: docs/interfaces/Frontend_Panes_Tooltip.TooltipTriggerProps.md ================================================ # Interface: TooltipTriggerProps [Frontend/Panes/Tooltip](../modules/Frontend_Panes_Tooltip.md).TooltipTriggerProps Each {@link TooltipName} has a corresponding tooltip element. ## Hierarchy - **`TooltipTriggerProps`** ↳ [`TooltipProps`](Frontend_Panes_Tooltip.TooltipProps.md) ## Table of contents ### Properties - [children](Frontend_Panes_Tooltip.TooltipTriggerProps.md#children) - [extraContent](Frontend_Panes_Tooltip.TooltipTriggerProps.md#extracontent) - [name](Frontend_Panes_Tooltip.TooltipTriggerProps.md#name) - [style](Frontend_Panes_Tooltip.TooltipTriggerProps.md#style) ## Properties ### children • **children**: `ReactNode` A [TooltipTrigger](../modules/Frontend_Panes_Tooltip.md#tooltiptrigger) wraps this child, and causes a tooltip to appear when the user hovers over it. --- ### extraContent • `Optional` **extraContent**: `ReactNode` You can append some dynamic content to the given tooltip by setting this field to a React node. --- ### name • **name**: `undefined` \| `TooltipName` The name of the tooltip element to display. You can see all the concrete tooltip contents in the file called {@link TooltipPanes}. Set to `undefined` to not render the tooltip. --- ### style • `Optional` **style**: `CSSProperties` You can optionally style the tooltip trigger element, not the tooltip itself. ================================================ FILE: docs/interfaces/Frontend_Utils_EmitterUtils.Diff.md ================================================ # Interface: Diff [Frontend/Utils/EmitterUtils](../modules/Frontend_Utils_EmitterUtils.md).Diff **`param`** The previously emitted state of an object **`param`** The current emitted state of an object ## Type parameters | Name | | :----- | | `Type` | ## Table of contents ### Properties - [current](Frontend_Utils_EmitterUtils.Diff.md#current) - [previous](Frontend_Utils_EmitterUtils.Diff.md#previous) ## Properties ### current • **current**: `Type` --- ### previous • **previous**: `Type` ================================================ FILE: docs/interfaces/Frontend_Views_ModalPane.ModalFrame.md ================================================ # Interface: ModalFrame [Frontend/Views/ModalPane](../modules/Frontend_Views_ModalPane.md).ModalFrame A modal has a {@code content}, and also optionally many {@link ModalFrames} pushed on top of it. ## Table of contents ### Properties - [helpContent](Frontend_Views_ModalPane.ModalFrame.md#helpcontent) - [title](Frontend_Views_ModalPane.ModalFrame.md#title) ### Methods - [element](Frontend_Views_ModalPane.ModalFrame.md#element) ## Properties ### helpContent • `Optional` **helpContent**: `ReactElement`<`any`, `string` \| `JSXElementConstructor`<`any`\>\> --- ### title • **title**: `string` ## Methods ### element ▸ **element**(): `ReactElement`<`any`, `string` \| `JSXElementConstructor`<`any`\>\> #### Returns `ReactElement`<`any`, `string` \| `JSXElementConstructor`<`any`\>\> ================================================ FILE: docs/interfaces/Frontend_Views_ModalPane.ModalHandle.md ================================================ # Interface: ModalHandle [Frontend/Views/ModalPane](../modules/Frontend_Views_ModalPane.md).ModalHandle **`todo`** Add things like open, close, set position, etc. ## Table of contents ### Properties - [id](Frontend_Views_ModalPane.ModalHandle.md#id) - [isActive](Frontend_Views_ModalPane.ModalHandle.md#isactive) ### Methods - [pop](Frontend_Views_ModalPane.ModalHandle.md#pop) - [popAll](Frontend_Views_ModalPane.ModalHandle.md#popall) - [push](Frontend_Views_ModalPane.ModalHandle.md#push) ## Properties ### id • **id**: `string` --- ### isActive • **isActive**: `boolean` ## Methods ### pop ▸ **pop**(): `void` #### Returns `void` --- ### popAll ▸ **popAll**(): `void` #### Returns `void` --- ### push ▸ **push**(`frame`): `void` #### Parameters | Name | Type | | :------ | :----------------------------------------------------- | | `frame` | [`ModalFrame`](Frontend_Views_ModalPane.ModalFrame.md) | #### Returns `void` ================================================ FILE: docs/interfaces/Frontend_Views_Share.ShareProps.md ================================================ # Interface: ShareProps [Frontend/Views/Share](../modules/Frontend_Views_Share.md).ShareProps ## Type parameters | Name | | :--- | | `T` | ## Table of contents ### Methods - [children](Frontend_Views_Share.ShareProps.md#children) - [load](Frontend_Views_Share.ShareProps.md#load) ## Methods ### children ▸ **children**(`state`, `loading`, `error`): `ReactNode` #### Parameters | Name | Type | | :-------- | :--------------------- | | `state` | `undefined` \| `T` | | `loading` | `boolean` | | `error` | `undefined` \| `Error` | #### Returns `ReactNode` --- ### load ▸ **load**(`store`): `Promise`<`T`\> #### Parameters | Name | Type | | :------ | :----------------------------------------------------------------- | | `store` | [`default`](../classes/Backend_Storage_ReaderDataStore.default.md) | #### Returns `Promise`<`T`\> ================================================ FILE: docs/interfaces/Frontend_Views_Terminal.TerminalHandle.md ================================================ # Interface: TerminalHandle [Frontend/Views/Terminal](../modules/Frontend_Views_Terminal.md).TerminalHandle ## Table of contents ### Methods - [clear](Frontend_Views_Terminal.TerminalHandle.md#clear) - [focus](Frontend_Views_Terminal.TerminalHandle.md#focus) - [getInput](Frontend_Views_Terminal.TerminalHandle.md#getinput) - [newline](Frontend_Views_Terminal.TerminalHandle.md#newline) - [print](Frontend_Views_Terminal.TerminalHandle.md#print) - [printElement](Frontend_Views_Terminal.TerminalHandle.md#printelement) - [printLink](Frontend_Views_Terminal.TerminalHandle.md#printlink) - [printLoadingBar](Frontend_Views_Terminal.TerminalHandle.md#printloadingbar) - [printLoadingSpinner](Frontend_Views_Terminal.TerminalHandle.md#printloadingspinner) - [printShellLn](Frontend_Views_Terminal.TerminalHandle.md#printshellln) - [println](Frontend_Views_Terminal.TerminalHandle.md#println) - [removeLast](Frontend_Views_Terminal.TerminalHandle.md#removelast) - [setInput](Frontend_Views_Terminal.TerminalHandle.md#setinput) - [setUserInputEnabled](Frontend_Views_Terminal.TerminalHandle.md#setuserinputenabled) ## Methods ### clear ▸ **clear**(): `void` #### Returns `void` --- ### focus ▸ **focus**(): `void` #### Returns `void` --- ### getInput ▸ **getInput**(): `Promise`<`string`\> #### Returns `Promise`<`string`\> --- ### newline ▸ **newline**(): `void` #### Returns `void` --- ### print ▸ **print**(`str`, `style?`): `void` #### Parameters | Name | Type | | :------- | :-------------------------------------------------------------------------------- | | `str` | `string` | | `style?` | [`TerminalTextStyle`](../enums/Frontend_Utils_TerminalTypes.TerminalTextStyle.md) | #### Returns `void` --- ### printElement ▸ **printElement**(`element`): `void` #### Parameters | Name | Type | | :-------- | :------------------------------------------------------------------ | | `element` | `ReactElement`<`any`, `string` \| `JSXElementConstructor`<`any`\>\> | #### Returns `void` --- ### printLink ▸ **printLink**(`str`, `onClick`, `style`): `void` #### Parameters | Name | Type | | :-------- | :-------------------------------------------------------------------------------- | | `str` | `string` | | `onClick` | () => `void` | | `style` | [`TerminalTextStyle`](../enums/Frontend_Utils_TerminalTypes.TerminalTextStyle.md) | #### Returns `void` --- ### printLoadingBar ▸ **printLoadingBar**(`prettyEntityName`, `ref`): `void` #### Parameters | Name | Type | | :----------------- | :----------------------------------------------------------------------------------------- | | `prettyEntityName` | `string` | | `ref` | `RefObject`<[`LoadingBarHandle`](Frontend_Components_TextLoadingBar.LoadingBarHandle.md)\> | #### Returns `void` --- ### printLoadingSpinner ▸ **printLoadingSpinner**(): `void` #### Returns `void` --- ### printShellLn ▸ **printShellLn**(`str`): `void` #### Parameters | Name | Type | | :---- | :------- | | `str` | `string` | #### Returns `void` --- ### println ▸ **println**(`str`, `style?`): `void` #### Parameters | Name | Type | | :------- | :-------------------------------------------------------------------------------- | | `str` | `string` | | `style?` | [`TerminalTextStyle`](../enums/Frontend_Utils_TerminalTypes.TerminalTextStyle.md) | #### Returns `void` --- ### removeLast ▸ **removeLast**(`n`): `void` #### Parameters | Name | Type | | :--- | :------- | | `n` | `number` | #### Returns `void` --- ### setInput ▸ **setInput**(`input`): `void` #### Parameters | Name | Type | | :------ | :------- | | `input` | `string` | #### Returns `void` --- ### setUserInputEnabled ▸ **setUserInputEnabled**(`enabled`): `void` #### Parameters | Name | Type | | :-------- | :-------- | | `enabled` | `boolean` | #### Returns `void` ================================================ FILE: docs/interfaces/Frontend_Views_Terminal.TerminalProps.md ================================================ # Interface: TerminalProps [Frontend/Views/Terminal](../modules/Frontend_Views_Terminal.md).TerminalProps ## Table of contents ### Properties - [promptCharacter](Frontend_Views_Terminal.TerminalProps.md#promptcharacter) ## Properties ### promptCharacter • **promptCharacter**: `string` ================================================ FILE: docs/interfaces/types_darkforest_api_ChunkStoreTypes.ChunkStore.md ================================================ # Interface: ChunkStore [\_types/darkforest/api/ChunkStoreTypes](../modules/types_darkforest_api_ChunkStoreTypes.md).ChunkStore Abstract interface shared between different types of chunk stores. Currently we have one that writes to IndexedDB, and one that simply throws away the data. ## Implemented by - [`HomePlanetMinerChunkStore`](../classes/Backend_Miner_MinerManager.HomePlanetMinerChunkStore.md) - [`default`](../classes/Backend_Storage_PersistentChunkStore.default.md) ## Table of contents ### Methods - [hasMinedChunk](types_darkforest_api_ChunkStoreTypes.ChunkStore.md#hasminedchunk) ## Methods ### hasMinedChunk ▸ **hasMinedChunk**(`chunkFootprint`): `boolean` #### Parameters | Name | Type | | :--------------- | :---------- | | `chunkFootprint` | `Rectangle` | #### Returns `boolean` ================================================ FILE: docs/interfaces/types_darkforest_api_ChunkStoreTypes.PersistedChunk.md ================================================ # Interface: PersistedChunk [\_types/darkforest/api/ChunkStoreTypes](../modules/types_darkforest_api_ChunkStoreTypes.md).PersistedChunk Chunks represent map data in some rectangle. This type represents a chunk when it is at rest in IndexedDB. The reason for this type's existence is that we want to reduce the amount of data we store on the user's computer. Shorter names hopefully means less data. ## Table of contents ### Properties - [l](types_darkforest_api_ChunkStoreTypes.PersistedChunk.md#l) - [p](types_darkforest_api_ChunkStoreTypes.PersistedChunk.md#p) - [s](types_darkforest_api_ChunkStoreTypes.PersistedChunk.md#s) - [x](types_darkforest_api_ChunkStoreTypes.PersistedChunk.md#x) - [y](types_darkforest_api_ChunkStoreTypes.PersistedChunk.md#y) ## Properties ### l • **l**: [`PersistedLocation`](types_darkforest_api_ChunkStoreTypes.PersistedLocation.md)[] --- ### p • **p**: `number` --- ### s • **s**: `number` --- ### x • **x**: `number` --- ### y • **y**: `number` ================================================ FILE: docs/interfaces/types_darkforest_api_ChunkStoreTypes.PersistedLocation.md ================================================ # Interface: PersistedLocation [\_types/darkforest/api/ChunkStoreTypes](../modules/types_darkforest_api_ChunkStoreTypes.md).PersistedLocation A location is a point sample of the universe. This type represents that point sample at rest when it is stored in IndexedDB. ## Table of contents ### Properties - [b](types_darkforest_api_ChunkStoreTypes.PersistedLocation.md#b) - [h](types_darkforest_api_ChunkStoreTypes.PersistedLocation.md#h) - [p](types_darkforest_api_ChunkStoreTypes.PersistedLocation.md#p) - [x](types_darkforest_api_ChunkStoreTypes.PersistedLocation.md#x) - [y](types_darkforest_api_ChunkStoreTypes.PersistedLocation.md#y) ## Properties ### b • **b**: `number` --- ### h • **h**: `LocationId` --- ### p • **p**: `number` --- ### x • **x**: `number` --- ### y • **y**: `number` ================================================ FILE: docs/interfaces/types_darkforest_api_ContractsAPITypes.ContractConstants.md ================================================ # Interface: ContractConstants [\_types/darkforest/api/ContractsAPITypes](../modules/types_darkforest_api_ContractsAPITypes.md).ContractConstants ## Table of contents ### Properties - [ABANDON_RANGE_CHANGE_PERCENT](types_darkforest_api_ContractsAPITypes.ContractConstants.md#abandon_range_change_percent) - [ABANDON_SPEED_CHANGE_PERCENT](types_darkforest_api_ContractsAPITypes.ContractConstants.md#abandon_speed_change_percent) - [ADMIN_CAN_ADD_PLANETS](types_darkforest_api_ContractsAPITypes.ContractConstants.md#admin_can_add_planets) - [ARTIFACT_POINT_VALUES](types_darkforest_api_ContractsAPITypes.ContractConstants.md#artifact_point_values) - [BIOMEBASE_KEY](types_darkforest_api_ContractsAPITypes.ContractConstants.md#biomebase_key) - [BIOME_THRESHOLD_1](types_darkforest_api_ContractsAPITypes.ContractConstants.md#biome_threshold_1) - [BIOME_THRESHOLD_2](types_darkforest_api_ContractsAPITypes.ContractConstants.md#biome_threshold_2) - [CAPTURE_ZONES_ENABLED](types_darkforest_api_ContractsAPITypes.ContractConstants.md#capture_zones_enabled) - [CAPTURE_ZONES_PER_5000_WORLD_RADIUS](types_darkforest_api_ContractsAPITypes.ContractConstants.md#capture_zones_per_5000_world_radius) - [CAPTURE_ZONE_CHANGE_BLOCK_INTERVAL](types_darkforest_api_ContractsAPITypes.ContractConstants.md#capture_zone_change_block_interval) - [CAPTURE_ZONE_COUNT](types_darkforest_api_ContractsAPITypes.ContractConstants.md#capture_zone_count) - [CAPTURE_ZONE_HOLD_BLOCKS_REQUIRED](types_darkforest_api_ContractsAPITypes.ContractConstants.md#capture_zone_hold_blocks_required) - [CAPTURE_ZONE_PLANET_LEVEL_SCORE](types_darkforest_api_ContractsAPITypes.ContractConstants.md#capture_zone_planet_level_score) - [CAPTURE_ZONE_RADIUS](types_darkforest_api_ContractsAPITypes.ContractConstants.md#capture_zone_radius) - [CLAIM_PLANET_COOLDOWN](types_darkforest_api_ContractsAPITypes.ContractConstants.md#claim_planet_cooldown) - [DISABLE_ZK_CHECKS](types_darkforest_api_ContractsAPITypes.ContractConstants.md#disable_zk_checks) - [GAME_START_BLOCK](types_darkforest_api_ContractsAPITypes.ContractConstants.md#game_start_block) - [INIT_PERLIN_MAX](types_darkforest_api_ContractsAPITypes.ContractConstants.md#init_perlin_max) - [INIT_PERLIN_MIN](types_darkforest_api_ContractsAPITypes.ContractConstants.md#init_perlin_min) - [LOCATION_REVEAL_COOLDOWN](types_darkforest_api_ContractsAPITypes.ContractConstants.md#location_reveal_cooldown) - [MAX_NATURAL_PLANET_LEVEL](types_darkforest_api_ContractsAPITypes.ContractConstants.md#max_natural_planet_level) - [PERLIN_LENGTH_SCALE](types_darkforest_api_ContractsAPITypes.ContractConstants.md#perlin_length_scale) - [PERLIN_MIRROR_X](types_darkforest_api_ContractsAPITypes.ContractConstants.md#perlin_mirror_x) - [PERLIN_MIRROR_Y](types_darkforest_api_ContractsAPITypes.ContractConstants.md#perlin_mirror_y) - [PERLIN_THRESHOLD_1](types_darkforest_api_ContractsAPITypes.ContractConstants.md#perlin_threshold_1) - [PERLIN_THRESHOLD_2](types_darkforest_api_ContractsAPITypes.ContractConstants.md#perlin_threshold_2) - [PERLIN_THRESHOLD_3](types_darkforest_api_ContractsAPITypes.ContractConstants.md#perlin_threshold_3) - [PHOTOID_ACTIVATION_DELAY](types_darkforest_api_ContractsAPITypes.ContractConstants.md#photoid_activation_delay) - [PLANETHASH_KEY](types_darkforest_api_ContractsAPITypes.ContractConstants.md#planethash_key) - [PLANET_LEVEL_JUNK](types_darkforest_api_ContractsAPITypes.ContractConstants.md#planet_level_junk) - [PLANET_LEVEL_THRESHOLDS](types_darkforest_api_ContractsAPITypes.ContractConstants.md#planet_level_thresholds) - [PLANET_RARITY](types_darkforest_api_ContractsAPITypes.ContractConstants.md#planet_rarity) - [PLANET_TRANSFER_ENABLED](types_darkforest_api_ContractsAPITypes.ContractConstants.md#planet_transfer_enabled) - [PLANET_TYPE_WEIGHTS](types_darkforest_api_ContractsAPITypes.ContractConstants.md#planet_type_weights) - [SILVER_SCORE_VALUE](types_darkforest_api_ContractsAPITypes.ContractConstants.md#silver_score_value) - [SPACETYPE_KEY](types_darkforest_api_ContractsAPITypes.ContractConstants.md#spacetype_key) - [SPACE_JUNK_ENABLED](types_darkforest_api_ContractsAPITypes.ContractConstants.md#space_junk_enabled) - [SPACE_JUNK_LIMIT](types_darkforest_api_ContractsAPITypes.ContractConstants.md#space_junk_limit) - [SPAWN_RIM_AREA](types_darkforest_api_ContractsAPITypes.ContractConstants.md#spawn_rim_area) - [TIME_FACTOR_HUNDREDTHS](types_darkforest_api_ContractsAPITypes.ContractConstants.md#time_factor_hundredths) - [TOKEN_MINT_END_SECONDS](types_darkforest_api_ContractsAPITypes.ContractConstants.md#token_mint_end_seconds) - [WORLD_RADIUS_LOCKED](types_darkforest_api_ContractsAPITypes.ContractConstants.md#world_radius_locked) - [WORLD_RADIUS_MIN](types_darkforest_api_ContractsAPITypes.ContractConstants.md#world_radius_min) - [adminAddress](types_darkforest_api_ContractsAPITypes.ContractConstants.md#adminaddress) - [defaultBarbarianPercentage](types_darkforest_api_ContractsAPITypes.ContractConstants.md#defaultbarbarianpercentage) - [defaultDefense](types_darkforest_api_ContractsAPITypes.ContractConstants.md#defaultdefense) - [defaultPopulationCap](types_darkforest_api_ContractsAPITypes.ContractConstants.md#defaultpopulationcap) - [defaultPopulationGrowth](types_darkforest_api_ContractsAPITypes.ContractConstants.md#defaultpopulationgrowth) - [defaultRange](types_darkforest_api_ContractsAPITypes.ContractConstants.md#defaultrange) - [defaultSilverCap](types_darkforest_api_ContractsAPITypes.ContractConstants.md#defaultsilvercap) - [defaultSilverGrowth](types_darkforest_api_ContractsAPITypes.ContractConstants.md#defaultsilvergrowth) - [defaultSpeed](types_darkforest_api_ContractsAPITypes.ContractConstants.md#defaultspeed) - [planetCumulativeRarities](types_darkforest_api_ContractsAPITypes.ContractConstants.md#planetcumulativerarities) - [planetLevelThresholds](types_darkforest_api_ContractsAPITypes.ContractConstants.md#planetlevelthresholds) - [upgrades](types_darkforest_api_ContractsAPITypes.ContractConstants.md#upgrades) ## Properties ### ABANDON_RANGE_CHANGE_PERCENT • **ABANDON_RANGE_CHANGE_PERCENT**: `number` The range boost a movement receives when abandoning a planet. --- ### ABANDON_SPEED_CHANGE_PERCENT • **ABANDON_SPEED_CHANGE_PERCENT**: `number` The speed boost a movement receives when abandoning a planet. --- ### ADMIN_CAN_ADD_PLANETS • **ADMIN_CAN_ADD_PLANETS**: `boolean` --- ### ARTIFACT_POINT_VALUES • **ARTIFACT_POINT_VALUES**: `ArtifactPointValues` --- ### BIOMEBASE_KEY • **BIOMEBASE_KEY**: `number` --- ### BIOME_THRESHOLD_1 • **BIOME_THRESHOLD_1**: `number` --- ### BIOME_THRESHOLD_2 • **BIOME_THRESHOLD_2**: `number` --- ### CAPTURE_ZONES_ENABLED • **CAPTURE_ZONES_ENABLED**: `boolean` --- ### CAPTURE_ZONES_PER_5000_WORLD_RADIUS • **CAPTURE_ZONES_PER_5000_WORLD_RADIUS**: `number` --- ### CAPTURE_ZONE_CHANGE_BLOCK_INTERVAL • **CAPTURE_ZONE_CHANGE_BLOCK_INTERVAL**: `number` --- ### CAPTURE_ZONE_COUNT • **CAPTURE_ZONE_COUNT**: `number` --- ### CAPTURE_ZONE_HOLD_BLOCKS_REQUIRED • **CAPTURE_ZONE_HOLD_BLOCKS_REQUIRED**: `number` --- ### CAPTURE_ZONE_PLANET_LEVEL_SCORE • **CAPTURE_ZONE_PLANET_LEVEL_SCORE**: [`number`, `number`, `number`, `number`, `number`, `number`, `number`, `number`, `number`, `number`] --- ### CAPTURE_ZONE_RADIUS • **CAPTURE_ZONE_RADIUS**: `number` --- ### CLAIM_PLANET_COOLDOWN • `Optional` **CLAIM_PLANET_COOLDOWN**: `number` --- ### DISABLE_ZK_CHECKS • **DISABLE_ZK_CHECKS**: `boolean` --- ### GAME_START_BLOCK • **GAME_START_BLOCK**: `number` --- ### INIT_PERLIN_MAX • **INIT_PERLIN_MAX**: `number` --- ### INIT_PERLIN_MIN • **INIT_PERLIN_MIN**: `number` --- ### LOCATION_REVEAL_COOLDOWN • **LOCATION_REVEAL_COOLDOWN**: `number` --- ### MAX_NATURAL_PLANET_LEVEL • **MAX_NATURAL_PLANET_LEVEL**: `number` --- ### PERLIN_LENGTH_SCALE • **PERLIN_LENGTH_SCALE**: `number` --- ### PERLIN_MIRROR_X • **PERLIN_MIRROR_X**: `boolean` --- ### PERLIN_MIRROR_Y • **PERLIN_MIRROR_Y**: `boolean` --- ### PERLIN_THRESHOLD_1 • **PERLIN_THRESHOLD_1**: `number` The perlin value at each coordinate determines the space type. There are four space types, which means there are four ranges on the number line that correspond to each space type. This function returns the boundary values between each of these four ranges: `PERLIN_THRESHOLD_1`, `PERLIN_THRESHOLD_2`, `PERLIN_THRESHOLD_3`. --- ### PERLIN_THRESHOLD_2 • **PERLIN_THRESHOLD_2**: `number` --- ### PERLIN_THRESHOLD_3 • **PERLIN_THRESHOLD_3**: `number` --- ### PHOTOID_ACTIVATION_DELAY • **PHOTOID_ACTIVATION_DELAY**: `number` --- ### PLANETHASH_KEY • **PLANETHASH_KEY**: `number` --- ### PLANET_LEVEL_JUNK • **PLANET_LEVEL_JUNK**: [`number`, `number`, `number`, `number`, `number`, `number`, `number`, `number`, `number`, `number`] The amount of junk that each level of planet gives the player when moving to it for the first time. --- ### PLANET_LEVEL_THRESHOLDS • **PLANET_LEVEL_THRESHOLDS**: [`number`, `number`, `number`, `number`, `number`, `number`, `number`, `number`, `number`, `number`] The chance for a planet to be a specific level. Each index corresponds to a planet level (index 5 is level 5 planet). The lower the number the lower the chance. Note: This does not control if a planet spawns or not, just the level when it spawns. --- ### PLANET_RARITY • **PLANET_RARITY**: `number` --- ### PLANET_TRANSFER_ENABLED • **PLANET_TRANSFER_ENABLED**: `boolean` --- ### PLANET_TYPE_WEIGHTS • **PLANET_TYPE_WEIGHTS**: [`PlanetTypeWeightsBySpaceType`](../modules/types_darkforest_api_ContractsAPITypes.md#planettypeweightsbyspacetype) --- ### SILVER_SCORE_VALUE • **SILVER_SCORE_VALUE**: `number` How much score silver gives when withdrawing. Expressed as a percentage integer. (100 is 100%) --- ### SPACETYPE_KEY • **SPACETYPE_KEY**: `number` --- ### SPACE_JUNK_ENABLED • **SPACE_JUNK_ENABLED**: `boolean` --- ### SPACE_JUNK_LIMIT • **SPACE_JUNK_LIMIT**: `number` Total amount of space junk a player can take on. This can be overridden at runtime by updating this value for a specific player in storage. --- ### SPAWN_RIM_AREA • **SPAWN_RIM_AREA**: `number` --- ### TIME_FACTOR_HUNDREDTHS • **TIME_FACTOR_HUNDREDTHS**: `number` --- ### TOKEN_MINT_END_SECONDS • **TOKEN_MINT_END_SECONDS**: `number` --- ### WORLD_RADIUS_LOCKED • **WORLD_RADIUS_LOCKED**: `boolean` --- ### WORLD_RADIUS_MIN • **WORLD_RADIUS_MIN**: `number` --- ### adminAddress • **adminAddress**: `EthAddress` --- ### defaultBarbarianPercentage • **defaultBarbarianPercentage**: `number`[] --- ### defaultDefense • **defaultDefense**: `number`[] --- ### defaultPopulationCap • **defaultPopulationCap**: `number`[] --- ### defaultPopulationGrowth • **defaultPopulationGrowth**: `number`[] --- ### defaultRange • **defaultRange**: `number`[] --- ### defaultSilverCap • **defaultSilverCap**: `number`[] --- ### defaultSilverGrowth • **defaultSilverGrowth**: `number`[] --- ### defaultSpeed • **defaultSpeed**: `number`[] --- ### planetCumulativeRarities • **planetCumulativeRarities**: `number`[] --- ### planetLevelThresholds • **planetLevelThresholds**: `number`[] --- ### upgrades • **upgrades**: `UpgradeBranches` ================================================ FILE: docs/interfaces/types_global_GlobalTypes.ClaimCountdownInfo.md ================================================ # Interface: ClaimCountdownInfo [\_types/global/GlobalTypes](../modules/types_global_GlobalTypes.md).ClaimCountdownInfo ## Table of contents ### Properties - [claimCooldownTime](types_global_GlobalTypes.ClaimCountdownInfo.md#claimcooldowntime) - [currentlyClaiming](types_global_GlobalTypes.ClaimCountdownInfo.md#currentlyclaiming) - [myLastClaimTimestamp](types_global_GlobalTypes.ClaimCountdownInfo.md#mylastclaimtimestamp) ## Properties ### claimCooldownTime • **claimCooldownTime**: `number` --- ### currentlyClaiming • **currentlyClaiming**: `boolean` --- ### myLastClaimTimestamp • `Optional` **myLastClaimTimestamp**: `number` ================================================ FILE: docs/interfaces/types_global_GlobalTypes.MinerWorkerMessage.md ================================================ # Interface: MinerWorkerMessage [\_types/global/GlobalTypes](../modules/types_global_GlobalTypes.md).MinerWorkerMessage ## Table of contents ### Properties - [biomebaseKey](types_global_GlobalTypes.MinerWorkerMessage.md#biomebasekey) - [chunkFootprint](types_global_GlobalTypes.MinerWorkerMessage.md#chunkfootprint) - [jobId](types_global_GlobalTypes.MinerWorkerMessage.md#jobid) - [perlinLengthScale](types_global_GlobalTypes.MinerWorkerMessage.md#perlinlengthscale) - [perlinMirrorX](types_global_GlobalTypes.MinerWorkerMessage.md#perlinmirrorx) - [perlinMirrorY](types_global_GlobalTypes.MinerWorkerMessage.md#perlinmirrory) - [planetHashKey](types_global_GlobalTypes.MinerWorkerMessage.md#planethashkey) - [planetRarity](types_global_GlobalTypes.MinerWorkerMessage.md#planetrarity) - [spaceTypeKey](types_global_GlobalTypes.MinerWorkerMessage.md#spacetypekey) - [totalWorkers](types_global_GlobalTypes.MinerWorkerMessage.md#totalworkers) - [useMockHash](types_global_GlobalTypes.MinerWorkerMessage.md#usemockhash) - [workerIndex](types_global_GlobalTypes.MinerWorkerMessage.md#workerindex) ## Properties ### biomebaseKey • **biomebaseKey**: `number` --- ### chunkFootprint • **chunkFootprint**: `Rectangle` --- ### jobId • **jobId**: `number` --- ### perlinLengthScale • **perlinLengthScale**: `number` --- ### perlinMirrorX • **perlinMirrorX**: `boolean` --- ### perlinMirrorY • **perlinMirrorY**: `boolean` --- ### planetHashKey • **planetHashKey**: `number` --- ### planetRarity • **planetRarity**: `number` --- ### spaceTypeKey • **spaceTypeKey**: `number` --- ### totalWorkers • **totalWorkers**: `number` --- ### useMockHash • **useMockHash**: `boolean` --- ### workerIndex • **workerIndex**: `number` ================================================ FILE: docs/interfaces/types_global_GlobalTypes.RevealCountdownInfo.md ================================================ # Interface: RevealCountdownInfo [\_types/global/GlobalTypes](../modules/types_global_GlobalTypes.md).RevealCountdownInfo ## Table of contents ### Properties - [currentlyRevealing](types_global_GlobalTypes.RevealCountdownInfo.md#currentlyrevealing) - [myLastRevealTimestamp](types_global_GlobalTypes.RevealCountdownInfo.md#mylastrevealtimestamp) - [revealCooldownTime](types_global_GlobalTypes.RevealCountdownInfo.md#revealcooldowntime) ## Properties ### currentlyRevealing • **currentlyRevealing**: `boolean` --- ### myLastRevealTimestamp • `Optional` **myLastRevealTimestamp**: `number` --- ### revealCooldownTime • **revealCooldownTime**: `number` ================================================ FILE: docs/modules/Backend_GameLogic_ArrivalUtils.md ================================================ # Module: Backend/GameLogic/ArrivalUtils ## Table of contents ### Interfaces - [PlanetDiff](../interfaces/Backend_GameLogic_ArrivalUtils.PlanetDiff.md) ### Functions - [applyUpgrade](Backend_GameLogic_ArrivalUtils.md#applyupgrade) - [arrive](Backend_GameLogic_ArrivalUtils.md#arrive) - [blocksLeftToProspectExpiration](Backend_GameLogic_ArrivalUtils.md#blockslefttoprospectexpiration) - [getEmojiMessage](Backend_GameLogic_ArrivalUtils.md#getemojimessage) - [isFindable](Backend_GameLogic_ArrivalUtils.md#isfindable) - [isProspectable](Backend_GameLogic_ArrivalUtils.md#isprospectable) - [prospectExpired](Backend_GameLogic_ArrivalUtils.md#prospectexpired) - [updatePlanetToTime](Backend_GameLogic_ArrivalUtils.md#updateplanettotime) ## Functions ### applyUpgrade ▸ **applyUpgrade**(`planet`, `upgrade`, `unApply?`): `void` #### Parameters | Name | Type | Default value | | :-------- | :-------- | :------------ | | `planet` | `Planet` | `undefined` | | `upgrade` | `Upgrade` | `undefined` | | `unApply` | `boolean` | `false` | #### Returns `void` --- ### arrive ▸ **arrive**(`toPlanet`, `artifactsOnPlanet`, `arrival`, `arrivingArtifact`, `contractConstants`): [`PlanetDiff`](../interfaces/Backend_GameLogic_ArrivalUtils.PlanetDiff.md) #### Parameters | Name | Type | | :------------------ | :----------------------------------------------------------------------------------------------- | | `toPlanet` | `Planet` | | `artifactsOnPlanet` | `Artifact`[] | | `arrival` | `QueuedArrival` | | `arrivingArtifact` | `undefined` \| `Artifact` | | `contractConstants` | [`ContractConstants`](../interfaces/types_darkforest_api_ContractsAPITypes.ContractConstants.md) | #### Returns [`PlanetDiff`](../interfaces/Backend_GameLogic_ArrivalUtils.PlanetDiff.md) --- ### blocksLeftToProspectExpiration ▸ **blocksLeftToProspectExpiration**(`currentBlockNumber`, `prospectedBlockNumber?`): `number` #### Parameters | Name | Type | | :----------------------- | :------- | | `currentBlockNumber` | `number` | | `prospectedBlockNumber?` | `number` | #### Returns `number` --- ### getEmojiMessage ▸ **getEmojiMessage**(`planet`): `PlanetMessage`<`EmojiFlagBody`\> \| `undefined` **`todo`** ArrivalUtils has become a dumping ground for functions that should just live inside of a `Planet` class. #### Parameters | Name | Type | | :------- | :---------------------- | | `planet` | `undefined` \| `Planet` | #### Returns `PlanetMessage`<`EmojiFlagBody`\> \| `undefined` --- ### isFindable ▸ **isFindable**(`planet`, `currentBlockNumber?`): `boolean` #### Parameters | Name | Type | | :-------------------- | :------- | | `planet` | `Planet` | | `currentBlockNumber?` | `number` | #### Returns `boolean` --- ### isProspectable ▸ **isProspectable**(`planet`): `boolean` #### Parameters | Name | Type | | :------- | :------- | | `planet` | `Planet` | #### Returns `boolean` --- ### prospectExpired ▸ **prospectExpired**(`currentBlockNumber`, `prospectedBlockNumber`): `boolean` #### Parameters | Name | Type | | :---------------------- | :------- | | `currentBlockNumber` | `number` | | `prospectedBlockNumber` | `number` | #### Returns `boolean` --- ### updatePlanetToTime ▸ **updatePlanetToTime**(`planet`, `planetArtifacts`, `atTimeMillis`, `contractConstants`, `setPlanet?`): `void` #### Parameters | Name | Type | | :------------------ | :----------------------------------------------------------------------------------------------- | | `planet` | `Planet` | | `planetArtifacts` | `Artifact`[] | | `atTimeMillis` | `number` | | `contractConstants` | [`ContractConstants`](../interfaces/types_darkforest_api_ContractsAPITypes.ContractConstants.md) | | `setPlanet` | (`p`: `Planet`) => `void` | #### Returns `void` ================================================ FILE: docs/modules/Backend_GameLogic_CaptureZoneGenerator.md ================================================ # Module: Backend/GameLogic/CaptureZoneGenerator ## Table of contents ### Classes - [CaptureZoneGenerator](../classes/Backend_GameLogic_CaptureZoneGenerator.CaptureZoneGenerator.md) ### Type aliases - [CaptureZonesGeneratedEvent](Backend_GameLogic_CaptureZoneGenerator.md#capturezonesgeneratedevent) ## Type aliases ### CaptureZonesGeneratedEvent Ƭ **CaptureZonesGeneratedEvent**: `Object` #### Type declaration | Name | Type | | :---------------- | :-------------- | | `changeBlock` | `number` | | `nextChangeBlock` | `number` | | `zones` | `CaptureZone`[] | ================================================ FILE: docs/modules/Backend_GameLogic_ContractsAPI.md ================================================ # Module: Backend/GameLogic/ContractsAPI ## Table of contents ### Classes - [ContractsAPI](../classes/Backend_GameLogic_ContractsAPI.ContractsAPI.md) ### Functions - [makeContractsAPI](Backend_GameLogic_ContractsAPI.md#makecontractsapi) ## Functions ### makeContractsAPI ▸ **makeContractsAPI**(`__namedParameters`): `Promise`<[`ContractsAPI`](../classes/Backend_GameLogic_ContractsAPI.ContractsAPI.md)\> #### Parameters | Name | Type | | :------------------ | :------------------- | | `__namedParameters` | `ContractsApiConfig` | #### Returns `Promise`<[`ContractsAPI`](../classes/Backend_GameLogic_ContractsAPI.ContractsAPI.md)\> ================================================ FILE: docs/modules/Backend_GameLogic_GameManager.md ================================================ # Module: Backend/GameLogic/GameManager ## Table of contents ### Enumerations - [GameManagerEvent](../enums/Backend_GameLogic_GameManager.GameManagerEvent.md) ### Classes - [default](../classes/Backend_GameLogic_GameManager.default.md) ================================================ FILE: docs/modules/Backend_GameLogic_GameObjects.md ================================================ # Module: Backend/GameLogic/GameObjects ## Table of contents ### Classes - [GameObjects](../classes/Backend_GameLogic_GameObjects.GameObjects.md) ================================================ FILE: docs/modules/Backend_GameLogic_GameUIManager.md ================================================ # Module: Backend/GameLogic/GameUIManager ## Table of contents ### Enumerations - [GameUIManagerEvent](../enums/Backend_GameLogic_GameUIManager.GameUIManagerEvent.md) ### Classes - [default](../classes/Backend_GameLogic_GameUIManager.default.md) ================================================ FILE: docs/modules/Backend_GameLogic_InitialGameStateDownloader.md ================================================ # Module: Backend/GameLogic/InitialGameStateDownloader ## Table of contents ### Classes - [InitialGameStateDownloader](../classes/Backend_GameLogic_InitialGameStateDownloader.InitialGameStateDownloader.md) ### Interfaces - [InitialGameState](../interfaces/Backend_GameLogic_InitialGameStateDownloader.InitialGameState.md) ================================================ FILE: docs/modules/Backend_GameLogic_LayeredMap.md ================================================ # Module: Backend/GameLogic/LayeredMap ## Table of contents ### Classes - [LayeredMap](../classes/Backend_GameLogic_LayeredMap.LayeredMap.md) ================================================ FILE: docs/modules/Backend_GameLogic_PluginManager.md ================================================ # Module: Backend/GameLogic/PluginManager ## Table of contents ### Classes - [PluginManager](../classes/Backend_GameLogic_PluginManager.PluginManager.md) - [ProcessInfo](../classes/Backend_GameLogic_PluginManager.ProcessInfo.md) ================================================ FILE: docs/modules/Backend_GameLogic_TutorialManager.md ================================================ # Module: Backend/GameLogic/TutorialManager ## Table of contents ### Enumerations - [TutorialManagerEvent](../enums/Backend_GameLogic_TutorialManager.TutorialManagerEvent.md) - [TutorialState](../enums/Backend_GameLogic_TutorialManager.TutorialState.md) ### Classes - [default](../classes/Backend_GameLogic_TutorialManager.default.md) ================================================ FILE: docs/modules/Backend_GameLogic_ViewportEntities.md ================================================ # Module: Backend/GameLogic/ViewportEntities ## Table of contents ### Classes - [ViewportEntities](../classes/Backend_GameLogic_ViewportEntities.ViewportEntities.md) ================================================ FILE: docs/modules/Backend_Miner_ChunkUtils.md ================================================ # Module: Backend/Miner/ChunkUtils ## Table of contents ### Functions - [addToChunkMap](Backend_Miner_ChunkUtils.md#addtochunkmap) - [getBucket](Backend_Miner_ChunkUtils.md#getbucket) - [getChunkKey](Backend_Miner_ChunkUtils.md#getchunkkey) - [getChunkOfSideLengthContainingPoint](Backend_Miner_ChunkUtils.md#getchunkofsidelengthcontainingpoint) - [getSiblingLocations](Backend_Miner_ChunkUtils.md#getsiblinglocations) - [toExploredChunk](Backend_Miner_ChunkUtils.md#toexploredchunk) - [toPersistedChunk](Backend_Miner_ChunkUtils.md#topersistedchunk) ## Functions ### addToChunkMap ▸ **addToChunkMap**(`existingChunks`, `newChunk`, `onAdd?`, `onRemove?`, `maxChunkSize?`): `void` At a high level, call this function to update an efficient quadtree-like store containing all of the chunks that a player has either mined or imported in their client. More speecifically, adds the given new chunk to the given map of chunks. If the map of chunks contains all of the "sibling" chunks to this new chunk, then instead of adding it, we merge the 4 sibling chunks, and add the merged chunk to the map and remove the existing sibling chunks. This function is recursive, which means that if the newly created merged chunk can also be merged with its siblings, then we merge it, add the new larger chunk, and also remove the previously existing sibling chunks. The maximum chunk size is represented by the `maxChunkSize` parameter (which has to be a power of two). If no `maxChunkSize` parameter is provided, then there is no maxmimum chunk size, meaning that chunks will be merged until no further merging is possible. `onAdd` and `onRemove` are called for each of the chunks that we add and remove to/from the `existingChunks` map. `onAdd` will be called exactly once, whereas `onRemove` only ever be called for sibling chunks that existed prior to this function being called. #### Parameters | Name | Type | | :--------------- | :---------------------------------------------------------------------------- | | `existingChunks` | `Map`<[`ChunkId`](types_darkforest_api_ChunkStoreTypes.md#chunkid), `Chunk`\> | | `newChunk` | `Chunk` | | `onAdd?` | (`arg`: `Chunk`) => `void` | | `onRemove?` | (`arg`: `Chunk`) => `void` | | `maxChunkSize?` | `number` | #### Returns `void` --- ### getBucket ▸ **getBucket**(`chunk`): [`BucketId`](types_darkforest_api_ChunkStoreTypes.md#bucketid) Deterministically assigns a bucket ID to a rectangle, based on its position and size in the universe. This is kind of like a shitty hash function. Its purpose is to distribute chunks roughly evenly between the buckets. #### Parameters | Name | Type | | :------ | :---------- | | `chunk` | `Rectangle` | #### Returns [`BucketId`](types_darkforest_api_ChunkStoreTypes.md#bucketid) --- ### getChunkKey ▸ **getChunkKey**(`chunkLoc`): [`ChunkId`](types_darkforest_api_ChunkStoreTypes.md#chunkid) A unique ID generated for each chunk based on its rectangle, as well as its bucket. It's the primary key by which chunks are identified. #### Parameters | Name | Type | | :--------- | :---------- | | `chunkLoc` | `Rectangle` | #### Returns [`ChunkId`](types_darkforest_api_ChunkStoreTypes.md#chunkid) --- ### getChunkOfSideLengthContainingPoint ▸ **getChunkOfSideLengthContainingPoint**(`coords`, `sideLength`): `Rectangle` Returns the unique aligned chunk (for definition of "aligned" see comment on `getSiblingLocations`) with the given side length that contains the given point. A chunk contains all of the points strictly inside of its bounds, as well as the bottom and left edges. This means it does not contain points which are on its right or top edges. #### Parameters | Name | Type | | :----------- | :------------ | | `coords` | `WorldCoords` | | `sideLength` | `number` | #### Returns `Rectangle` --- ### getSiblingLocations ▸ **getSiblingLocations**(`chunkLoc`): [`Rectangle`, `Rectangle`, `Rectangle`] An aligned chunk is one whose corner's coordinates are multiples of its side length, and its side length is a power of two between [MIN_CHUNK_SIZE](Frontend_Utils_constants.md#min_chunk_size) and [MAX_CHUNK_SIZE](Frontend_Utils_constants.md#max_chunk_size) inclusive. "Aligned" chunks is that they can be merged into other aligned chunks. Non-aligned chunks cannot always be merged into squares. The reason we care about merging is that merging chunks allows us to represent more world-space using fewer chunks. This saves memory at both runtime and storage-time. Therefore, we only store aligned chunks. As an example, chunks with any corner at (0, 0) are always aligned. A chunk with side length 4 is aligned if it's on (4, 4), (8, 12), but not (4, 6). This function returns the other three chunks with the same side length of the given chunk, such that the four chunks, if merged, would result in an "aligned" chunk whose side length is double the given chunk. #### Parameters | Name | Type | | :--------- | :---------- | | `chunkLoc` | `Rectangle` | #### Returns [`Rectangle`, `Rectangle`, `Rectangle`] --- ### toExploredChunk ▸ **toExploredChunk**(`chunk`): `Chunk` Converts from the persisted representation of a chunk to the in-game representation of a chunk. #### Parameters | Name | Type | | :------ | :--------------------------------------------------------------------------------------- | | `chunk` | [`PersistedChunk`](../interfaces/types_darkforest_api_ChunkStoreTypes.PersistedChunk.md) | #### Returns `Chunk` --- ### toPersistedChunk ▸ **toPersistedChunk**(`chunk`): [`PersistedChunk`](../interfaces/types_darkforest_api_ChunkStoreTypes.PersistedChunk.md) Converts from the in-game representation of a chunk to its persisted representation. #### Parameters | Name | Type | | :------ | :------ | | `chunk` | `Chunk` | #### Returns [`PersistedChunk`](../interfaces/types_darkforest_api_ChunkStoreTypes.PersistedChunk.md) ================================================ FILE: docs/modules/Backend_Miner_MinerManager.md ================================================ # Module: Backend/Miner/MinerManager ## Table of contents ### Enumerations - [MinerManagerEvent](../enums/Backend_Miner_MinerManager.MinerManagerEvent.md) ### Classes - [HomePlanetMinerChunkStore](../classes/Backend_Miner_MinerManager.HomePlanetMinerChunkStore.md) - [default](../classes/Backend_Miner_MinerManager.default.md) ### Type aliases - [workerFactory](Backend_Miner_MinerManager.md#workerfactory) ## Type aliases ### workerFactory Ƭ **workerFactory**: () => `Worker` #### Type declaration ▸ (): `Worker` ##### Returns `Worker` ================================================ FILE: docs/modules/Backend_Miner_MiningPatterns.md ================================================ # Module: Backend/Miner/MiningPatterns ## Table of contents ### Enumerations - [MiningPatternType](../enums/Backend_Miner_MiningPatterns.MiningPatternType.md) ### Classes - [SpiralPattern](../classes/Backend_Miner_MiningPatterns.SpiralPattern.md) - [SwissCheesePattern](../classes/Backend_Miner_MiningPatterns.SwissCheesePattern.md) - [TowardsCenterPattern](../classes/Backend_Miner_MiningPatterns.TowardsCenterPattern.md) - [TowardsCenterPatternV2](../classes/Backend_Miner_MiningPatterns.TowardsCenterPatternV2.md) ### Interfaces - [MiningPattern](../interfaces/Backend_Miner_MiningPatterns.MiningPattern.md) ================================================ FILE: docs/modules/Backend_Miner_permutation.md ================================================ # Module: Backend/Miner/permutation ## Table of contents ### Functions - [getPlanetLocations](Backend_Miner_permutation.md#getplanetlocations) ## Functions ### getPlanetLocations ▸ **getPlanetLocations**(`spaceTypeKey`, `biomebaseKey`, `perlinLengthScale`, `perlinMirrorX`, `perlinMirrorY`): (`chunkFootprint`: `Rectangle`, `planetRarity`: `number`) => `WorldLocation`[] #### Parameters | Name | Type | | :------------------ | :-------- | | `spaceTypeKey` | `number` | | `biomebaseKey` | `number` | | `perlinLengthScale` | `number` | | `perlinMirrorX` | `boolean` | | `perlinMirrorY` | `boolean` | #### Returns `fn` ▸ (`chunkFootprint`, `planetRarity`): `WorldLocation`[] ##### Parameters | Name | Type | | :--------------- | :---------- | | `chunkFootprint` | `Rectangle` | | `planetRarity` | `number` | ##### Returns `WorldLocation`[] ================================================ FILE: docs/modules/Backend_Network_AccountManager.md ================================================ # Module: Backend/Network/AccountManager ## Table of contents ### Interfaces - [Account](../interfaces/Backend_Network_AccountManager.Account.md) ### Functions - [addAccount](Backend_Network_AccountManager.md#addaccount) - [getAccounts](Backend_Network_AccountManager.md#getaccounts) ## Functions ### addAccount ▸ **addAccount**(`privateKey`): `void` Adds the given account, and saves it to localstorage. #### Parameters | Name | Type | | :----------- | :------- | | `privateKey` | `string` | #### Returns `void` --- ### getAccounts ▸ **getAccounts**(): [`Account`](../interfaces/Backend_Network_AccountManager.Account.md)[] Returns the list of accounts that are logged into the game. #### Returns [`Account`](../interfaces/Backend_Network_AccountManager.Account.md)[] ================================================ FILE: docs/modules/Backend_Network_Blockchain.md ================================================ # Module: Backend/Network/Blockchain ## Table of contents ### Functions - [getEthConnection](Backend_Network_Blockchain.md#getethconnection) - [loadDiamondContract](Backend_Network_Blockchain.md#loaddiamondcontract) ## Functions ### getEthConnection ▸ **getEthConnection**(): `Promise`<`EthConnection`\> #### Returns `Promise`<`EthConnection`\> --- ### loadDiamondContract ▸ **loadDiamondContract**<`T`\>(`address`, `provider`, `signer?`): `Promise`<`T`\> Loads the game contract, which is responsible for updating the state of the game. #### Type parameters | Name | Type | | :--- | :----------------------- | | `T` | extends `Contract`<`T`\> | #### Parameters | Name | Type | | :--------- | :---------------- | | `address` | `string` | | `provider` | `JsonRpcProvider` | | `signer?` | `Wallet` | #### Returns `Promise`<`T`\> ================================================ FILE: docs/modules/Backend_Network_EventLogger.md ================================================ # Module: Backend/Network/EventLogger ## Table of contents ### Enumerations - [EventType](../enums/Backend_Network_EventLogger.EventType.md) ### Classes - [EventLogger](../classes/Backend_Network_EventLogger.EventLogger.md) ### Variables - [eventLogger](Backend_Network_EventLogger.md#eventlogger) ## Variables ### eventLogger • `Const` **eventLogger**: [`EventLogger`](../classes/Backend_Network_EventLogger.EventLogger.md) ================================================ FILE: docs/modules/Backend_Network_LeaderboardApi.md ================================================ # Module: Backend/Network/LeaderboardApi ## Table of contents ### Functions - [loadLeaderboard](Backend_Network_LeaderboardApi.md#loadleaderboard) ## Functions ### loadLeaderboard ▸ **loadLeaderboard**(): `Promise`<`Leaderboard`\> #### Returns `Promise`<`Leaderboard`\> ================================================ FILE: docs/modules/Backend_Network_MessageAPI.md ================================================ # Module: Backend/Network/MessageAPI ## Table of contents ### Functions - [addMessage](Backend_Network_MessageAPI.md#addmessage) - [deleteMessages](Backend_Network_MessageAPI.md#deletemessages) - [getMessagesOnPlanets](Backend_Network_MessageAPI.md#getmessagesonplanets) ## Functions ### addMessage ▸ **addMessage**(`request`): `Promise`<`void`\> #### Parameters | Name | Type | | :-------- | :------------------------------------------------- | | `request` | `SignedMessage`<`PostMessageRequest`<`unknown`\>\> | #### Returns `Promise`<`void`\> --- ### deleteMessages ▸ **deleteMessages**(`request`): `Promise`<`void`\> #### Parameters | Name | Type | | :-------- | :---------------------------------------- | | `request` | `SignedMessage`<`DeleteMessagesRequest`\> | #### Returns `Promise`<`void`\> --- ### getMessagesOnPlanets ▸ **getMessagesOnPlanets**(`request`): `Promise`<`PlanetMessageResponse`\> #### Parameters | Name | Type | | :-------- | :--------------------- | | `request` | `PlanetMessageRequest` | #### Returns `Promise`<`PlanetMessageResponse`\> ================================================ FILE: docs/modules/Backend_Network_NetworkHealthApi.md ================================================ # Module: Backend/Network/NetworkHealthApi ## Table of contents ### Functions - [loadNetworkHealth](Backend_Network_NetworkHealthApi.md#loadnetworkhealth) ## Functions ### loadNetworkHealth ▸ **loadNetworkHealth**(): `Promise`<`NetworkHealthSummary`\> The Dark Forest webserver keeps track of network health, this function loads that information from the webserver. #### Returns `Promise`<`NetworkHealthSummary`\> ================================================ FILE: docs/modules/Backend_Network_UtilityServerAPI.md ================================================ # Module: Backend/Network/UtilityServerAPI ## Table of contents ### Enumerations - [EmailResponse](../enums/Backend_Network_UtilityServerAPI.EmailResponse.md) ### Type aliases - [RegisterConfirmationResponse](Backend_Network_UtilityServerAPI.md#registerconfirmationresponse) ### Functions - [callRegisterAndWaitForConfirmation](Backend_Network_UtilityServerAPI.md#callregisterandwaitforconfirmation) - [disconnectTwitter](Backend_Network_UtilityServerAPI.md#disconnecttwitter) - [getAllTwitters](Backend_Network_UtilityServerAPI.md#getalltwitters) - [requestDevFaucet](Backend_Network_UtilityServerAPI.md#requestdevfaucet) - [submitInterestedEmail](Backend_Network_UtilityServerAPI.md#submitinterestedemail) - [submitPlayerEmail](Backend_Network_UtilityServerAPI.md#submitplayeremail) - [submitUnsubscribeEmail](Backend_Network_UtilityServerAPI.md#submitunsubscribeemail) - [submitWhitelistKey](Backend_Network_UtilityServerAPI.md#submitwhitelistkey) - [tryGetAllTwitters](Backend_Network_UtilityServerAPI.md#trygetalltwitters) - [verifyTwitterHandle](Backend_Network_UtilityServerAPI.md#verifytwitterhandle) - [whitelistStatus](Backend_Network_UtilityServerAPI.md#whiteliststatus) ## Type aliases ### RegisterConfirmationResponse Ƭ **RegisterConfirmationResponse**: `Object` #### Type declaration | Name | Type | Description | | :-------------- | :-------- | :------------------------------------------------------------------------------------------------------- | | `canRetry?` | `boolean` | If the whitelist registration is unsuccessful, this is true if the client is able to retry registration. | | `errorMessage?` | `string` | If the whitelist registration is unsuccessful, this is populated with the error message explaining why. | | `txHash?` | `string` | If the whitelist registration is successful, this is populated with the hash of the transaction. | ## Functions ### callRegisterAndWaitForConfirmation ▸ **callRegisterAndWaitForConfirmation**(`key`, `address`, `terminal`): `Promise`<[`RegisterConfirmationResponse`](Backend_Network_UtilityServerAPI.md#registerconfirmationresponse)\> Starts the registration process for the user then polls for success. #### Parameters | Name | Type | | :--------- | :-------------------------------------------------------------------------------------------------------------- | | `key` | `string` | | `address` | `EthAddress` | | `terminal` | `MutableRefObject`<`undefined` \| [`TerminalHandle`](../interfaces/Frontend_Views_Terminal.TerminalHandle.md)\> | #### Returns `Promise`<[`RegisterConfirmationResponse`](Backend_Network_UtilityServerAPI.md#registerconfirmationresponse)\> --- ### disconnectTwitter ▸ **disconnectTwitter**(`disconnectMessage`): `Promise`<`boolean`\> #### Parameters | Name | Type | | :------------------ | :---------------------------------------- | | `disconnectMessage` | `SignedMessage`<{ `twitter`: `string` }\> | #### Returns `Promise`<`boolean`\> --- ### getAllTwitters ▸ **getAllTwitters**(): `Promise`<[`AddressTwitterMap`](types_darkforest_api_UtilityServerAPITypes.md#addresstwittermap)\> #### Returns `Promise`<[`AddressTwitterMap`](types_darkforest_api_UtilityServerAPITypes.md#addresstwittermap)\> --- ### requestDevFaucet ▸ **requestDevFaucet**(`address`): `Promise`<`boolean`\> #### Parameters | Name | Type | | :-------- | :----------- | | `address` | `EthAddress` | #### Returns `Promise`<`boolean`\> --- ### submitInterestedEmail ▸ **submitInterestedEmail**(`email`): `Promise`<[`EmailResponse`](../enums/Backend_Network_UtilityServerAPI.EmailResponse.md)\> #### Parameters | Name | Type | | :------ | :------- | | `email` | `string` | #### Returns `Promise`<[`EmailResponse`](../enums/Backend_Network_UtilityServerAPI.EmailResponse.md)\> --- ### submitPlayerEmail ▸ **submitPlayerEmail**(`request?`): `Promise`<[`EmailResponse`](../enums/Backend_Network_UtilityServerAPI.EmailResponse.md)\> #### Parameters | Name | Type | | :--------- | :-------------------------------------- | | `request?` | `SignedMessage`<{ `email`: `string` }\> | #### Returns `Promise`<[`EmailResponse`](../enums/Backend_Network_UtilityServerAPI.EmailResponse.md)\> --- ### submitUnsubscribeEmail ▸ **submitUnsubscribeEmail**(`email`): `Promise`<[`EmailResponse`](../enums/Backend_Network_UtilityServerAPI.EmailResponse.md)\> #### Parameters | Name | Type | | :------ | :------- | | `email` | `string` | #### Returns `Promise`<[`EmailResponse`](../enums/Backend_Network_UtilityServerAPI.EmailResponse.md)\> --- ### submitWhitelistKey ▸ **submitWhitelistKey**(`key`, `address`): `Promise`<`null` \| `RegisterResponse`\> Submits a whitelist key to register the given player to the game. Returns null if there was an error. #### Parameters | Name | Type | | :-------- | :----------- | | `key` | `string` | | `address` | `EthAddress` | #### Returns `Promise`<`null` \| `RegisterResponse`\> --- ### tryGetAllTwitters ▸ **tryGetAllTwitters**(): `Promise`<[`AddressTwitterMap`](types_darkforest_api_UtilityServerAPITypes.md#addresstwittermap)\> Swallows all errors. Either loads the address to twitter map from the webserver in 5 seconds, or returan empty map. #### Returns `Promise`<[`AddressTwitterMap`](types_darkforest_api_UtilityServerAPITypes.md#addresstwittermap)\> --- ### verifyTwitterHandle ▸ **verifyTwitterHandle**(`verifyMessage`): `Promise`<`boolean`\> #### Parameters | Name | Type | | :-------------- | :---------------------------------------- | | `verifyMessage` | `SignedMessage`<{ `twitter`: `string` }\> | #### Returns `Promise`<`boolean`\> --- ### whitelistStatus ▸ **whitelistStatus**(`address`): `Promise`<`null` \| `WhitelistStatusResponse`\> #### Parameters | Name | Type | | :-------- | :----------- | | `address` | `EthAddress` | #### Returns `Promise`<`null` \| `WhitelistStatusResponse`\> ================================================ FILE: docs/modules/Backend_Plugins_EmbeddedPluginLoader.md ================================================ # Module: Backend/Plugins/EmbeddedPluginLoader ## Table of contents ### Interfaces - [EmbeddedPlugin](../interfaces/Backend_Plugins_EmbeddedPluginLoader.EmbeddedPlugin.md) ### Functions - [getEmbeddedPlugins](Backend_Plugins_EmbeddedPluginLoader.md#getembeddedplugins) ## Functions ### getEmbeddedPlugins ▸ **getEmbeddedPlugins**(`isAdmin`): { `code`: `string` ; `id`: `PluginId` ; `name`: `string` }[] #### Parameters | Name | Type | | :-------- | :-------- | | `isAdmin` | `boolean` | #### Returns { `code`: `string` ; `id`: `PluginId` ; `name`: `string` }[] ================================================ FILE: docs/modules/Backend_Plugins_PluginProcess.md ================================================ # Module: Backend/Plugins/PluginProcess ## Table of contents ### Interfaces - [PluginProcess](../interfaces/Backend_Plugins_PluginProcess.PluginProcess.md) ================================================ FILE: docs/modules/Backend_Plugins_PluginTemplate.md ================================================ # Module: Backend/Plugins/PluginTemplate ## Table of contents ### Variables - [PLUGIN_TEMPLATE](Backend_Plugins_PluginTemplate.md#plugin_template) ## Variables ### PLUGIN_TEMPLATE • `Const` **PLUGIN_TEMPLATE**: `string` ================================================ FILE: docs/modules/Backend_Plugins_SerializedPlugin.md ================================================ # Module: Backend/Plugins/SerializedPlugin ## Table of contents ### Interfaces - [SerializedPlugin](../interfaces/Backend_Plugins_SerializedPlugin.SerializedPlugin.md) ================================================ FILE: docs/modules/Backend_Storage_PersistentChunkStore.md ================================================ # Module: Backend/Storage/PersistentChunkStore ## Table of contents ### Classes - [default](../classes/Backend_Storage_PersistentChunkStore.default.md) ### Variables - [MODAL_POSITIONS_KEY](Backend_Storage_PersistentChunkStore.md#modal_positions_key) ## Variables ### MODAL_POSITIONS_KEY • `Const` **MODAL_POSITIONS_KEY**: `"modal_positions"` ================================================ FILE: docs/modules/Backend_Storage_ReaderDataStore.md ================================================ # Module: Backend/Storage/ReaderDataStore ## Table of contents ### Enumerations - [SinglePlanetDataStoreEvent](../enums/Backend_Storage_ReaderDataStore.SinglePlanetDataStoreEvent.md) ### Classes - [default](../classes/Backend_Storage_ReaderDataStore.default.md) ================================================ FILE: docs/modules/Backend_Utils_Animation.md ================================================ # Module: Backend/Utils/Animation ## Table of contents ### Functions - [constantAnimation](Backend_Utils_Animation.md#constantanimation) - [easeInAnimation](Backend_Utils_Animation.md#easeinanimation) - [emojiEaseOutAnimation](Backend_Utils_Animation.md#emojieaseoutanimation) - [planetLevelToAnimationSpeed](Backend_Utils_Animation.md#planetleveltoanimationspeed) - [sinusoidalAnimation](Backend_Utils_Animation.md#sinusoidalanimation) ## Functions ### constantAnimation ▸ **constantAnimation**(`constant`): `DFAnimation` #### Parameters | Name | Type | | :--------- | :------- | | `constant` | `number` | #### Returns `DFAnimation` --- ### easeInAnimation ▸ **easeInAnimation**(`durationMs`, `delayMs?`): `DFAnimation` #### Parameters | Name | Type | | :----------- | :------- | | `durationMs` | `number` | | `delayMs?` | `number` | #### Returns `DFAnimation` --- ### emojiEaseOutAnimation ▸ **emojiEaseOutAnimation**(`durationMs`, `emoji`): `DFStatefulAnimation`<`string`\> #### Parameters | Name | Type | | :----------- | :------- | | `durationMs` | `number` | | `emoji` | `string` | #### Returns `DFStatefulAnimation`<`string`\> --- ### planetLevelToAnimationSpeed ▸ **planetLevelToAnimationSpeed**(`level`): `number` #### Parameters | Name | Type | | :------ | :------------ | | `level` | `PlanetLevel` | #### Returns `number` --- ### sinusoidalAnimation ▸ **sinusoidalAnimation**(`rps`): `DFAnimation` #### Parameters | Name | Type | | :---- | :------- | | `rps` | `number` | #### Returns `DFAnimation` ================================================ FILE: docs/modules/Backend_Utils_Coordinates.md ================================================ # Module: Backend/Utils/Coordinates ## Table of contents ### Functions - [coordsEqual](Backend_Utils_Coordinates.md#coordsequal) - [distL2](Backend_Utils_Coordinates.md#distl2) - [normalizeVector](Backend_Utils_Coordinates.md#normalizevector) - [scaleVector](Backend_Utils_Coordinates.md#scalevector) - [vectorLength](Backend_Utils_Coordinates.md#vectorlength) ## Functions ### coordsEqual ▸ **coordsEqual**(`a`, `b`): `boolean` #### Parameters | Name | Type | | :--- | :------------ | | `a` | `WorldCoords` | | `b` | `WorldCoords` | #### Returns `boolean` --- ### distL2 ▸ **distL2**(`a`, `b`): `number` #### Parameters | Name | Type | | :--- | :------------------------------ | | `a` | `WorldCoords` \| `CanvasCoords` | | `b` | `WorldCoords` \| `CanvasCoords` | #### Returns `number` --- ### normalizeVector ▸ **normalizeVector**(`a`): `WorldCoords` #### Parameters | Name | Type | | :--- | :------------ | | `a` | `WorldCoords` | #### Returns `WorldCoords` --- ### scaleVector ▸ **scaleVector**(`a`, `k`): `Object` #### Parameters | Name | Type | | :--- | :------------ | | `a` | `WorldCoords` | | `k` | `number` | #### Returns `Object` | Name | Type | | :--- | :------- | | `x` | `number` | | `y` | `number` | --- ### vectorLength ▸ **vectorLength**(`a`): `number` #### Parameters | Name | Type | | :--- | :------------------------------ | | `a` | `WorldCoords` \| `CanvasCoords` | #### Returns `number` ================================================ FILE: docs/modules/Backend_Utils_SnarkArgsHelper.md ================================================ # Module: Backend/Utils/SnarkArgsHelper ## Table of contents ### Classes - [default](../classes/Backend_Utils_SnarkArgsHelper.default.md) ================================================ FILE: docs/modules/Backend_Utils_Utils.md ================================================ # Module: Backend/Utils/Utils ## Table of contents ### Variables - [ONE_DAY](Backend_Utils_Utils.md#one_day) ### Functions - [getFormatProp](Backend_Utils_Utils.md#getformatprop) - [getOwnerColor](Backend_Utils_Utils.md#getownercolor) - [getPlanetMaxRank](Backend_Utils_Utils.md#getplanetmaxrank) - [getPlanetRank](Backend_Utils_Utils.md#getplanetrank) - [getPlanetShortHash](Backend_Utils_Utils.md#getplanetshorthash) - [getPlayerColor](Backend_Utils_Utils.md#getplayercolor) - [getPlayerShortHash](Backend_Utils_Utils.md#getplayershorthash) - [getRandomActionId](Backend_Utils_Utils.md#getrandomactionid) - [getUpgradeStat](Backend_Utils_Utils.md#getupgradestat) - [hexifyBigIntNestedArray](Backend_Utils_Utils.md#hexifybigintnestedarray) - [hslStr](Backend_Utils_Utils.md#hslstr) - [isFullRank](Backend_Utils_Utils.md#isfullrank) - [titleCase](Backend_Utils_Utils.md#titlecase) - [upgradeName](Backend_Utils_Utils.md#upgradename) ## Variables ### ONE_DAY • `Const` **ONE_DAY**: `number` ## Functions ### getFormatProp ▸ **getFormatProp**(`planet`, `prop`): `string` #### Parameters | Name | Type | | :------- | :---------------------- | | `planet` | `undefined` \| `Planet` | | `prop` | `string` | #### Returns `string` --- ### getOwnerColor ▸ **getOwnerColor**(`planet`): `string` #### Parameters | Name | Type | | :------- | :------- | | `planet` | `Planet` | #### Returns `string` --- ### getPlanetMaxRank ▸ **getPlanetMaxRank**(`planet`): `number` #### Parameters | Name | Type | | :------- | :---------------------- | | `planet` | `undefined` \| `Planet` | #### Returns `number` --- ### getPlanetRank ▸ **getPlanetRank**(`planet`): `number` #### Parameters | Name | Type | | :------- | :---------------------- | | `planet` | `undefined` \| `Planet` | #### Returns `number` --- ### getPlanetShortHash ▸ **getPlanetShortHash**(`planet`): `string` #### Parameters | Name | Type | | :------- | :---------------------- | | `planet` | `undefined` \| `Planet` | #### Returns `string` --- ### getPlayerColor ▸ **getPlayerColor**(`player`): `string` #### Parameters | Name | Type | | :------- | :----------- | | `player` | `EthAddress` | #### Returns `string` --- ### getPlayerShortHash ▸ **getPlayerShortHash**(`address`): `string` #### Parameters | Name | Type | | :-------- | :----------- | | `address` | `EthAddress` | #### Returns `string` --- ### getRandomActionId ▸ **getRandomActionId**(): `string` #### Returns `string` --- ### getUpgradeStat ▸ **getUpgradeStat**(`upgrade`, `stat`): `number` #### Parameters | Name | Type | | :-------- | :-------------------------------------------------------- | | `upgrade` | `Upgrade` | | `stat` | [`StatIdx`](../enums/types_global_GlobalTypes.StatIdx.md) | #### Returns `number` --- ### hexifyBigIntNestedArray ▸ **hexifyBigIntNestedArray**(`arr`): `NestedStringArray` #### Parameters | Name | Type | | :---- | :------------------ | | `arr` | `NestedBigIntArray` | #### Returns `NestedStringArray` --- ### hslStr ▸ **hslStr**(`h`, `s`, `l`): `string` #### Parameters | Name | Type | | :--- | :------- | | `h` | `number` | | `s` | `number` | | `l` | `number` | #### Returns `string` --- ### isFullRank ▸ **isFullRank**(`planet`): `boolean` #### Parameters | Name | Type | | :------- | :---------------------- | | `planet` | `undefined` \| `Planet` | #### Returns `boolean` --- ### titleCase ▸ **titleCase**(`title`): `string` #### Parameters | Name | Type | | :------ | :------- | | `title` | `string` | #### Returns `string` --- ### upgradeName ▸ **upgradeName**(`branchName`): `string` #### Parameters | Name | Type | | :----------- | :------------------ | | `branchName` | `UpgradeBranchName` | #### Returns `string` ================================================ FILE: docs/modules/Backend_Utils_WhitelistSnarkArgsHelper.md ================================================ # Module: Backend/Utils/WhitelistSnarkArgsHelper ## Table of contents ### Functions - [getWhitelistArgs](Backend_Utils_WhitelistSnarkArgsHelper.md#getwhitelistargs) ## Functions ### getWhitelistArgs ▸ **getWhitelistArgs**(`key`, `recipient`, `terminal?`): `Promise`<`WhitelistSnarkContractCallArgs`\> Helper method for generating whitelist SNARKS. This is separate from the existing {@link SnarkArgsHelper} because whitelist txs require far less setup compared to SNARKS that are sent in context of the game. #### Parameters | Name | Type | | :---------- | :-------------------------------------------------------------------------------------------------------------- | | `key` | `BigInteger` | | `recipient` | `EthAddress` | | `terminal?` | `MutableRefObject`<`undefined` \| [`TerminalHandle`](../interfaces/Frontend_Views_Terminal.TerminalHandle.md)\> | #### Returns `Promise`<`WhitelistSnarkContractCallArgs`\> ================================================ FILE: docs/modules/Backend_Utils_Wrapper.md ================================================ # Module: Backend/Utils/Wrapper ## Table of contents ### Classes - [Wrapper](../classes/Backend_Utils_Wrapper.Wrapper.md) ================================================ FILE: docs/modules/Frontend_Components_AncientLabel.md ================================================ # Module: Frontend/Components/AncientLabel ## Table of contents ### Variables - [ancientAnim](Frontend_Components_AncientLabel.md#ancientanim) ### Functions - [AncientLabel](Frontend_Components_AncientLabel.md#ancientlabel) - [AncientLabelAnim](Frontend_Components_AncientLabel.md#ancientlabelanim) ## Variables ### ancientAnim • `Const` **ancientAnim**: `FlattenSimpleInterpolation` ## Functions ### AncientLabel ▸ **AncientLabel**(): `Element` #### Returns `Element` --- ### AncientLabelAnim ▸ **AncientLabelAnim**(): `Element` #### Returns `Element` ================================================ FILE: docs/modules/Frontend_Components_ArtifactImage.md ================================================ # Module: Frontend/Components/ArtifactImage ## Table of contents ### Variables - [ARTIFACT_URL](Frontend_Components_ArtifactImage.md#artifact_url) ### Functions - [ArtifactImage](Frontend_Components_ArtifactImage.md#artifactimage) ## Variables ### ARTIFACT_URL • `Const` **ARTIFACT_URL**: `"https://d2wspbczt15cqu.cloudfront.net/v0.6.0-artifacts/"` ## Functions ### ArtifactImage ▸ **ArtifactImage**(`__namedParameters`): `Element` #### Parameters | Name | Type | | :--------------------------- | :------------------ | | `__namedParameters` | `Object` | | `__namedParameters.artifact` | `Artifact` | | `__namedParameters.bgColor?` | `ArtifactFileColor` | | `__namedParameters.size` | `number` | | `__namedParameters.thumb?` | `boolean` | #### Returns `Element` ================================================ FILE: docs/modules/Frontend_Components_BiomeAnims.md ================================================ # Module: Frontend/Components/BiomeAnims ## Table of contents ### Variables - [burnAnim](Frontend_Components_BiomeAnims.md#burnanim) - [icyAnim](Frontend_Components_BiomeAnims.md#icyanim) - [shakeAnim](Frontend_Components_BiomeAnims.md#shakeanim) - [wiggle](Frontend_Components_BiomeAnims.md#wiggle) ## Variables ### burnAnim • `Const` **burnAnim**: `FlattenSimpleInterpolation` --- ### icyAnim • `Const` **icyAnim**: `FlattenSimpleInterpolation` --- ### shakeAnim • `Const` **shakeAnim**: `FlattenSimpleInterpolation` --- ### wiggle • `Const` **wiggle**: `Keyframes` ================================================ FILE: docs/modules/Frontend_Components_Btn.md ================================================ # Module: Frontend/Components/Btn ## Table of contents ### Classes - [DarkForestButton](../classes/Frontend_Components_Btn.DarkForestButton.md) - [DarkForestShortcutButton](../classes/Frontend_Components_Btn.DarkForestShortcutButton.md) - [ShortcutPressedEvent](../classes/Frontend_Components_Btn.ShortcutPressedEvent.md) ### Variables - [Btn](Frontend_Components_Btn.md#btn) - [ShortcutBtn](Frontend_Components_Btn.md#shortcutbtn) ## Variables ### Btn • `Const` **Btn**: `ForwardRefExoticComponent`<`Partial`<`Omit`<[`DarkForestButton`](../classes/Frontend_Components_Btn.DarkForestButton.md), `"children"`\>\> & `Events`<{ `onClick`: (`evt`: `Event` & `MouseEvent`<[`DarkForestButton`](../classes/Frontend_Components_Btn.DarkForestButton.md), `MouseEvent`\>) => `void` }\> & `HTMLAttributes`<`HTMLElement`\> & {} & `RefAttributes`<`unknown`\>\> --- ### ShortcutBtn • `Const` **ShortcutBtn**: `ForwardRefExoticComponent`<`Partial`<`Omit`<[`DarkForestShortcutButton`](../classes/Frontend_Components_Btn.DarkForestShortcutButton.md), `"children"`\>\> & `Events`<{ `onClick`: (`evt`: `Event` & `MouseEvent`<[`DarkForestShortcutButton`](../classes/Frontend_Components_Btn.DarkForestShortcutButton.md), `MouseEvent`\>) => `void` ; `onShortcutPressed`: (`evt`: [`ShortcutPressedEvent`](../classes/Frontend_Components_Btn.ShortcutPressedEvent.md)) => `void` }\> & `HTMLAttributes`<`HTMLElement`\> & {} & `RefAttributes`<`unknown`\>\> ================================================ FILE: docs/modules/Frontend_Components_Button.md ================================================ # Module: Frontend/Components/Button ## Table of contents ### Functions - [default](Frontend_Components_Button.md#default) ## Functions ### default ▸ **default**(`__namedParameters`): `Element` #### Parameters | Name | Type | | :------------------ | :------------ | | `__namedParameters` | `ButtonProps` | #### Returns `Element` ================================================ FILE: docs/modules/Frontend_Components_CapturePlanetButton.md ================================================ # Module: Frontend/Components/CapturePlanetButton ## Table of contents ### Functions - [CapturePlanetButton](Frontend_Components_CapturePlanetButton.md#captureplanetbutton) ## Functions ### CapturePlanetButton ▸ **CapturePlanetButton**(`__namedParameters`): `Element` #### Parameters | Name | Type | | :-------------------------------- | :--------------------------------------------------------------------------------- | | `__namedParameters` | `Object` | | `__namedParameters.planetWrapper` | [`Wrapper`](../classes/Backend_Utils_Wrapper.Wrapper.md)<`undefined` \| `Planet`\> | #### Returns `Element` ================================================ FILE: docs/modules/Frontend_Components_CoreUI.md ================================================ # Module: Frontend/Components/CoreUI ## Table of contents ### Variables - [AlignCenterHorizontally](Frontend_Components_CoreUI.md#aligncenterhorizontally) - [AlignCenterVertically](Frontend_Components_CoreUI.md#aligncentervertically) - [BorderlessPane](Frontend_Components_CoreUI.md#borderlesspane) - [Bottom](Frontend_Components_CoreUI.md#bottom) - [CenterBackgroundSubtext](Frontend_Components_CoreUI.md#centerbackgroundsubtext) - [CenterRow](Frontend_Components_CoreUI.md#centerrow) - [Display](Frontend_Components_CoreUI.md#display) - [DontShrink](Frontend_Components_CoreUI.md#dontshrink) - [EmSpacer](Frontend_Components_CoreUI.md#emspacer) - [Emphasized](Frontend_Components_CoreUI.md#emphasized) - [Expand](Frontend_Components_CoreUI.md#expand) - [FloatRight](Frontend_Components_CoreUI.md#floatright) - [FullHeight](Frontend_Components_CoreUI.md#fullheight) - [HeaderText](Frontend_Components_CoreUI.md#headertext) - [Hidden](Frontend_Components_CoreUI.md#hidden) - [InlineBlock](Frontend_Components_CoreUI.md#inlineblock) - [MaxWidth](Frontend_Components_CoreUI.md#maxwidth) - [Padded](Frontend_Components_CoreUI.md#padded) - [PluginElements](Frontend_Components_CoreUI.md#pluginelements) - [Section](Frontend_Components_CoreUI.md#section) - [SectionHeader](Frontend_Components_CoreUI.md#sectionheader) - [Select](Frontend_Components_CoreUI.md#select) - [Separator](Frontend_Components_CoreUI.md#separator) - [Spacer](Frontend_Components_CoreUI.md#spacer) - [Spread](Frontend_Components_CoreUI.md#spread) - [SpreadApart](Frontend_Components_CoreUI.md#spreadapart) - [TextButton](Frontend_Components_CoreUI.md#textbutton) - [Title](Frontend_Components_CoreUI.md#title) - [Truncate](Frontend_Components_CoreUI.md#truncate) - [Underline](Frontend_Components_CoreUI.md#underline) ### Functions - [Link](Frontend_Components_CoreUI.md#link) - [SelectFrom](Frontend_Components_CoreUI.md#selectfrom) - [VerticalSplit](Frontend_Components_CoreUI.md#verticalsplit) ## Variables ### AlignCenterHorizontally • `Const` **AlignCenterHorizontally**: `StyledComponent`<`"div"`, `any`, {}, `never`\> Fills parent width, aligns children horizontally in the center. --- ### AlignCenterVertically • `Const` **AlignCenterVertically**: `StyledComponent`<`"div"`, `any`, {}, `never`\> --- ### BorderlessPane • `Const` **BorderlessPane**: `StyledComponent`<`"div"`, `any`, {}, `never`\> --- ### Bottom • `Const` **Bottom**: `StyledComponent`<`"div"`, `any`, {}, `never`\> --- ### CenterBackgroundSubtext • `Const` **CenterBackgroundSubtext**: `StyledComponent`<`"div"`, `any`, { `height`: `string` ; `width`: `string` }, `never`\> A box which centers some darkened text. Useful for displaying _somthing_ instead of empty space, if there isn't something to be displayed. Think of it as a placeholder. --- ### CenterRow • `Const` **CenterRow**: `StyledComponent`<`"div"`, `any`, {}, `never`\> --- ### Display • `Const` **Display**: `StyledComponent`<`"div"`, `any`, { `visible?`: `boolean` }, `never`\> --- ### DontShrink • `Const` **DontShrink**: `StyledComponent`<`"div"`, `any`, {}, `never`\> Don't shrink in a flexbox. --- ### EmSpacer • `Const` **EmSpacer**: `StyledComponent`<`"div"`, `any`, { `height?`: `number` ; `width?`: `number` }, `never`\> Inline block rectangle, measured in ems, default 1em by 1em. --- ### Emphasized • `Const` **Emphasized**: `StyledComponent`<`"span"`, `any`, {}, `never`\> --- ### Expand • `Const` **Expand**: `StyledComponent`<`"div"`, `any`, {}, `never`\> Expands to fill space in a flexbox. --- ### FloatRight • `Const` **FloatRight**: `StyledComponent`<`"div"`, `any`, {}, `never`\> --- ### FullHeight • `Const` **FullHeight**: `StyledComponent`<`"div"`, `any`, {}, `never`\> --- ### HeaderText • `Const` **HeaderText**: `StyledComponent`<`"div"`, `any`, {}, `never`\> --- ### Hidden • `Const` **Hidden**: `StyledComponent`<`"div"`, `any`, {}, `never`\> --- ### InlineBlock • `Const` **InlineBlock**: `StyledComponent`<`"div"`, `any`, {}, `never`\> --- ### MaxWidth • `Const` **MaxWidth**: `StyledComponent`<`"div"`, `any`, { `width`: `string` }, `never`\> --- ### Padded • `Const` **Padded**: `StyledComponent`<`"div"`, `any`, { `bottom?`: `string` ; `left?`: `string` ; `right?`: `string` ; `top?`: `string` }, `never`\> --- ### PluginElements • `Const` **PluginElements**: `StyledComponent`<`"div"`, `any`, {}, `never`\> The container element into which a plugin renders its html elements. Contains styles for child elements so that plugins can use UI that is consistent with the rest of Dark Forest's UI. Keeping this up to date will be an ongoing challange, but there's probably some better way to do this. --- ### Section • `Const` **Section**: `StyledComponent`<`"div"`, `any`, {}, `never`\> --- ### SectionHeader • `Const` **SectionHeader**: `StyledComponent`<`"div"`, `any`, {}, `never`\> --- ### Select • `Const` **Select**: `StyledComponent`<`"select"`, `any`, { `wide?`: `boolean` }, `never`\> --- ### Separator • `Const` **Separator**: `StyledComponent`<`"div"`, `any`, {}, `never`\> --- ### Spacer • `Const` **Spacer**: `StyledComponent`<`"div"`, `any`, { `height?`: `number` ; `width?`: `number` }, `never`\> --- ### Spread • `Const` **Spread**: `StyledComponent`<`"div"`, `any`, {}, `never`\> Expands to fit the width of container. Is itself a flex box that spreads out its children horizontally. --- ### SpreadApart • `Const` **SpreadApart**: `StyledComponent`<`"div"`, `any`, {}, `never`\> Expands to fit the width of container. Is itself a flex box that spreads out its children horizontally. --- ### TextButton • `Const` **TextButton**: `StyledComponent`<`"span"`, `any`, {}, `never`\> --- ### Title • `Const` **Title**: `StyledComponent`<`"div"`, `any`, { `maxWidth?`: `string` }, `never`\> --- ### Truncate • `Const` **Truncate**: `StyledComponent`<`"div"`, `any`, { `maxWidth?`: `string` }, `never`\> --- ### Underline • `Const` **Underline**: `StyledComponent`<`"span"`, `any`, {}, `never`\> ## Functions ### Link ▸ **Link**(`props`): `Element` This is the link that all core ui in Dark Forest should use. Please! #### Parameters | Name | Type | | :------ | :-------------------------------------------------------------------------------------------------------------------------------------------- | | `props` | { `children`: `ReactNode` ; `color?`: `string` ; `openInThisTab?`: `boolean` ; `to?`: `string` } & `HtmlHTMLAttributes`<`HTMLAnchorElement`\> | #### Returns `Element` --- ### SelectFrom ▸ **SelectFrom**(`__namedParameters`): `Element` Controllable input that allows the user to select from one of the given string values. #### Parameters | Name | Type | | :--------------------------- | :---------------------------- | | `__namedParameters` | `Object` | | `__namedParameters.labels` | `string`[] | | `__namedParameters.style?` | `CSSProperties` | | `__namedParameters.value` | `string` | | `__namedParameters.values` | `string`[] | | `__namedParameters.wide?` | `boolean` | | `__namedParameters.setValue` | (`value`: `string`) => `void` | #### Returns `Element` --- ### VerticalSplit ▸ **VerticalSplit**(`__namedParameters`): `Element` #### Parameters | Name | Type | | :--------------------------- | :------------------------- | | `__namedParameters` | `Object` | | `__namedParameters.children` | [`ReactNode`, `ReactNode`] | #### Returns `Element` ================================================ FILE: docs/modules/Frontend_Components_Corner.md ================================================ # Module: Frontend/Components/Corner ## Table of contents ### Functions - [Corner](Frontend_Components_Corner.md#corner) ## Functions ### Corner ▸ **Corner**(`__namedParameters`): `Element` #### Parameters | Name | Type | | :------------------ | :------------ | | `__namedParameters` | `CornerProps` | #### Returns `Element` ================================================ FILE: docs/modules/Frontend_Components_DisplayGasPrices.md ================================================ # Module: Frontend/Components/DisplayGasPrices ## Table of contents ### Functions - [DisplayGasPrices](Frontend_Components_DisplayGasPrices.md#displaygasprices) ## Functions ### DisplayGasPrices ▸ **DisplayGasPrices**(`__namedParameters`): `Element` #### Parameters | Name | Type | | :----------------------------- | :---------- | | `__namedParameters` | `Object` | | `__namedParameters.gasPrices?` | `GasPrices` | #### Returns `Element` ================================================ FILE: docs/modules/Frontend_Components_Email.md ================================================ # Module: Frontend/Components/Email ## Table of contents ### Enumerations - [EmailCTAMode](../enums/Frontend_Components_Email.EmailCTAMode.md) ### Functions - [EmailCTA](Frontend_Components_Email.md#emailcta) ## Functions ### EmailCTA ▸ **EmailCTA**(`__namedParameters`): `Element` #### Parameters | Name | Type | | :----------------------- | :------------------------------------------------------------------- | | `__namedParameters` | `Object` | | `__namedParameters.mode` | [`EmailCTAMode`](../enums/Frontend_Components_Email.EmailCTAMode.md) | #### Returns `Element` ================================================ FILE: docs/modules/Frontend_Components_GameLandingPageComponents.md ================================================ # Module: Frontend/Components/GameLandingPageComponents ## Table of contents ### Enumerations - [InitRenderState](../enums/Frontend_Components_GameLandingPageComponents.InitRenderState.md) ### Variables - [Hidden](Frontend_Components_GameLandingPageComponents.md#hidden) ### Functions - [GameWindowWrapper](Frontend_Components_GameLandingPageComponents.md#gamewindowwrapper) - [TerminalToggler](Frontend_Components_GameLandingPageComponents.md#terminaltoggler) - [TerminalWrapper](Frontend_Components_GameLandingPageComponents.md#terminalwrapper) - [Wrapper](Frontend_Components_GameLandingPageComponents.md#wrapper) ## Variables ### Hidden • `Const` **Hidden**: `StyledComponent`<`"div"`, `any`, {}, `never`\> ## Functions ### GameWindowWrapper ▸ **GameWindowWrapper**(`__namedParameters`): `Element` #### Parameters | Name | Type | | :------------------ | :-------------------- | | `__namedParameters` | `LandingWrapperProps` | #### Returns `Element` --- ### TerminalToggler ▸ **TerminalToggler**(`__namedParameters`): `Element` #### Parameters | Name | Type | | :------------------------------------- | :---------------------------------------- | | `__namedParameters` | `Object` | | `__namedParameters.setTerminalEnabled` | `Dispatch`<`SetStateAction`<`boolean`\>\> | | `__namedParameters.terminalEnabled` | `boolean` | #### Returns `Element` --- ### TerminalWrapper ▸ **TerminalWrapper**(`__namedParameters`): `Element` #### Parameters | Name | Type | | :------------------ | :-------------------- | | `__namedParameters` | `LandingWrapperProps` | #### Returns `Element` --- ### Wrapper ▸ **Wrapper**(`__namedParameters`): `Element` #### Parameters | Name | Type | | :------------------ | :-------------------- | | `__namedParameters` | `LandingWrapperProps` | #### Returns `Element` ================================================ FILE: docs/modules/Frontend_Components_GameWindowComponents.md ================================================ # Module: Frontend/Components/GameWindowComponents ## Table of contents ### Type aliases - [PaneProps](Frontend_Components_GameWindowComponents.md#paneprops) ### Variables - [CanvasContainer](Frontend_Components_GameWindowComponents.md#canvascontainer) - [CanvasWrapper](Frontend_Components_GameWindowComponents.md#canvaswrapper) - [MainWindow](Frontend_Components_GameWindowComponents.md#mainwindow) - [StyledPane](Frontend_Components_GameWindowComponents.md#styledpane) - [UpperLeft](Frontend_Components_GameWindowComponents.md#upperleft) - [WindowWrapper](Frontend_Components_GameWindowComponents.md#windowwrapper) ## Type aliases ### PaneProps Ƭ **PaneProps**: `Object` #### Type declaration | Name | Type | | :------------- | :---------------------------------------------------- | | `children` | `React.ReactNode` | | `headerItems?` | `React.ReactNode` | | `title` | `string` \| (`small`: `boolean`) => `React.ReactNode` | ## Variables ### CanvasContainer • `Const` **CanvasContainer**: `StyledComponent`<`"div"`, `any`, {}, `never`\> --- ### CanvasWrapper • `Const` **CanvasWrapper**: `StyledComponent`<`"div"`, `any`, {}, `never`\> --- ### MainWindow • `Const` **MainWindow**: `StyledComponent`<`"div"`, `any`, {}, `never`\> --- ### StyledPane • `Const` **StyledPane**: `StyledComponent`<`"div"`, `any`, {}, `never`\> --- ### UpperLeft • `Const` **UpperLeft**: `StyledComponent`<`"div"`, `any`, {}, `never`\> --- ### WindowWrapper • `Const` **WindowWrapper**: `StyledComponent`<`"div"`, `any`, {}, `never`\> ================================================ FILE: docs/modules/Frontend_Components_Icons.md ================================================ # Module: Frontend/Components/Icons ## Table of contents ### Type aliases - [IconType](Frontend_Components_Icons.md#icontype) ### Variables - [Icon](Frontend_Components_Icons.md#icon) - [IconType](Frontend_Components_Icons.md#icontype) ### Functions - [ArtifactFound](Frontend_Components_Icons.md#artifactfound) - [ArtifactProspected](Frontend_Components_Icons.md#artifactprospected) - [BranchIcon](Frontend_Components_Icons.md#branchicon) - [FoundCorrupted](Frontend_Components_Icons.md#foundcorrupted) - [FoundDeadSpace](Frontend_Components_Icons.md#founddeadspace) - [FoundDeepSpace](Frontend_Components_Icons.md#founddeepspace) - [FoundDesert](Frontend_Components_Icons.md#founddesert) - [FoundForest](Frontend_Components_Icons.md#foundforest) - [FoundGrassland](Frontend_Components_Icons.md#foundgrassland) - [FoundIce](Frontend_Components_Icons.md#foundice) - [FoundLava](Frontend_Components_Icons.md#foundlava) - [FoundOcean](Frontend_Components_Icons.md#foundocean) - [FoundPirates](Frontend_Components_Icons.md#foundpirates) - [FoundRuins](Frontend_Components_Icons.md#foundruins) - [FoundSilver](Frontend_Components_Icons.md#foundsilver) - [FoundSpace](Frontend_Components_Icons.md#foundspace) - [FoundSpacetimeRip](Frontend_Components_Icons.md#foundspacetimerip) - [FoundSwamp](Frontend_Components_Icons.md#foundswamp) - [FoundTradingPost](Frontend_Components_Icons.md#foundtradingpost) - [FoundTundra](Frontend_Components_Icons.md#foundtundra) - [FoundWasteland](Frontend_Components_Icons.md#foundwasteland) - [Generic](Frontend_Components_Icons.md#generic) - [MetPlayer](Frontend_Components_Icons.md#metplayer) - [PlanetAttacked](Frontend_Components_Icons.md#planetattacked) - [PlanetConquered](Frontend_Components_Icons.md#planetconquered) - [PlanetLost](Frontend_Components_Icons.md#planetlost) - [Quasar](Frontend_Components_Icons.md#quasar) - [RankIcon](Frontend_Components_Icons.md#rankicon) - [StatIcon](Frontend_Components_Icons.md#staticon) - [TxAccepted](Frontend_Components_Icons.md#txaccepted) - [TxConfirmed](Frontend_Components_Icons.md#txconfirmed) - [TxDeclined](Frontend_Components_Icons.md#txdeclined) - [TxInitialized](Frontend_Components_Icons.md#txinitialized) ## Type aliases ### IconType Ƭ **IconType**: `Abstract`<`string`, `"IconType"`\> ## Variables ### Icon • `Const` **Icon**: `ForwardRefExoticComponent`<`Partial`<`Omit`<`DarkForestIcon`, `"children"`\>\> & `Events`<`unknown`\> & `HTMLAttributes`<`HTMLElement`\> & {} & `RefAttributes`<`unknown`\>\> --- ### IconType • **IconType**: `Object` #### Type declaration | Name | Type | | :------------- | :-------------------------------------------------- | | `Activate` | [`IconType`](Frontend_Components_Icons.md#icontype) | | `Artifact` | [`IconType`](Frontend_Components_Icons.md#icontype) | | `Broadcast` | [`IconType`](Frontend_Components_Icons.md#icontype) | | `Capturable` | [`IconType`](Frontend_Components_Icons.md#icontype) | | `Check` | [`IconType`](Frontend_Components_Icons.md#icontype) | | `Deactivate` | [`IconType`](Frontend_Components_Icons.md#icontype) | | `Defense` | [`IconType`](Frontend_Components_Icons.md#icontype) | | `Deposit` | [`IconType`](Frontend_Components_Icons.md#icontype) | | `Destroyed` | [`IconType`](Frontend_Components_Icons.md#icontype) | | `DoubleArrows` | [`IconType`](Frontend_Components_Icons.md#icontype) | | `Energy` | [`IconType`](Frontend_Components_Icons.md#icontype) | | `EnergyGrowth` | [`IconType`](Frontend_Components_Icons.md#icontype) | | `FastForward` | [`IconType`](Frontend_Components_Icons.md#icontype) | | `Hat` | [`IconType`](Frontend_Components_Icons.md#icontype) | | `Help` | [`IconType`](Frontend_Components_Icons.md#icontype) | | `Invadable` | [`IconType`](Frontend_Components_Icons.md#icontype) | | `Leaderboard` | [`IconType`](Frontend_Components_Icons.md#icontype) | | `Lock` | [`IconType`](Frontend_Components_Icons.md#icontype) | | `MaxLevel` | [`IconType`](Frontend_Components_Icons.md#icontype) | | `Pause` | [`IconType`](Frontend_Components_Icons.md#icontype) | | `Pirates` | [`IconType`](Frontend_Components_Icons.md#icontype) | | `Planet` | [`IconType`](Frontend_Components_Icons.md#icontype) | | `PlanetDex` | [`IconType`](Frontend_Components_Icons.md#icontype) | | `Play` | [`IconType`](Frontend_Components_Icons.md#icontype) | | `Plugin` | [`IconType`](Frontend_Components_Icons.md#icontype) | | `Range` | [`IconType`](Frontend_Components_Icons.md#icontype) | | `RankFour` | [`IconType`](Frontend_Components_Icons.md#icontype) | | `RankMax` | [`IconType`](Frontend_Components_Icons.md#icontype) | | `RankOne` | [`IconType`](Frontend_Components_Icons.md#icontype) | | `RankThree` | [`IconType`](Frontend_Components_Icons.md#icontype) | | `RankTwo` | [`IconType`](Frontend_Components_Icons.md#icontype) | | `Refresh` | [`IconType`](Frontend_Components_Icons.md#icontype) | | `RightArrow` | [`IconType`](Frontend_Components_Icons.md#icontype) | | `Settings` | [`IconType`](Frontend_Components_Icons.md#icontype) | | `Share` | [`IconType`](Frontend_Components_Icons.md#icontype) | | `Silver` | [`IconType`](Frontend_Components_Icons.md#icontype) | | `SilverGrowth` | [`IconType`](Frontend_Components_Icons.md#icontype) | | `SilverProd` | [`IconType`](Frontend_Components_Icons.md#icontype) | | `Sparkles` | [`IconType`](Frontend_Components_Icons.md#icontype) | | `Speed` | [`IconType`](Frontend_Components_Icons.md#icontype) | | `Target` | [`IconType`](Frontend_Components_Icons.md#icontype) | | `TrashCan` | [`IconType`](Frontend_Components_Icons.md#icontype) | | `Twitter` | [`IconType`](Frontend_Components_Icons.md#icontype) | | `Upgrade` | [`IconType`](Frontend_Components_Icons.md#icontype) | | `Withdraw` | [`IconType`](Frontend_Components_Icons.md#icontype) | | `X` | [`IconType`](Frontend_Components_Icons.md#icontype) | ## Functions ### ArtifactFound ▸ **ArtifactFound**(`__namedParameters`): `Element` #### Parameters | Name | Type | | :------------------ | :---------- | | `__namedParameters` | `AlertIcon` | #### Returns `Element` --- ### ArtifactProspected ▸ **ArtifactProspected**(`__namedParameters`): `Element` #### Parameters | Name | Type | | :------------------ | :---------- | | `__namedParameters` | `AlertIcon` | #### Returns `Element` --- ### BranchIcon ▸ **BranchIcon**(`__namedParameters`): `Element` #### Parameters | Name | Type | | :------------------------- | :------- | | `__namedParameters` | `Object` | | `__namedParameters.branch` | `number` | #### Returns `Element` --- ### FoundCorrupted ▸ **FoundCorrupted**(`__namedParameters`): `Element` #### Parameters | Name | Type | | :------------------ | :---------- | | `__namedParameters` | `AlertIcon` | #### Returns `Element` --- ### FoundDeadSpace ▸ **FoundDeadSpace**(`__namedParameters`): `Element` #### Parameters | Name | Type | | :------------------ | :---------- | | `__namedParameters` | `AlertIcon` | #### Returns `Element` --- ### FoundDeepSpace ▸ **FoundDeepSpace**(`__namedParameters`): `Element` #### Parameters | Name | Type | | :------------------ | :---------- | | `__namedParameters` | `AlertIcon` | #### Returns `Element` --- ### FoundDesert ▸ **FoundDesert**(`__namedParameters`): `Element` #### Parameters | Name | Type | | :------------------ | :---------- | | `__namedParameters` | `AlertIcon` | #### Returns `Element` --- ### FoundForest ▸ **FoundForest**(`__namedParameters`): `Element` #### Parameters | Name | Type | | :------------------ | :---------- | | `__namedParameters` | `AlertIcon` | #### Returns `Element` --- ### FoundGrassland ▸ **FoundGrassland**(`__namedParameters`): `Element` #### Parameters | Name | Type | | :------------------ | :---------- | | `__namedParameters` | `AlertIcon` | #### Returns `Element` --- ### FoundIce ▸ **FoundIce**(`__namedParameters`): `Element` #### Parameters | Name | Type | | :------------------ | :---------- | | `__namedParameters` | `AlertIcon` | #### Returns `Element` --- ### FoundLava ▸ **FoundLava**(`__namedParameters`): `Element` #### Parameters | Name | Type | | :------------------ | :---------- | | `__namedParameters` | `AlertIcon` | #### Returns `Element` --- ### FoundOcean ▸ **FoundOcean**(`__namedParameters`): `Element` #### Parameters | Name | Type | | :------------------ | :---------- | | `__namedParameters` | `AlertIcon` | #### Returns `Element` --- ### FoundPirates ▸ **FoundPirates**(`__namedParameters`): `Element` #### Parameters | Name | Type | | :------------------ | :---------- | | `__namedParameters` | `AlertIcon` | #### Returns `Element` --- ### FoundRuins ▸ **FoundRuins**(`__namedParameters`): `Element` #### Parameters | Name | Type | | :------------------ | :---------- | | `__namedParameters` | `AlertIcon` | #### Returns `Element` --- ### FoundSilver ▸ **FoundSilver**(`__namedParameters`): `Element` #### Parameters | Name | Type | | :------------------ | :---------- | | `__namedParameters` | `AlertIcon` | #### Returns `Element` --- ### FoundSpace ▸ **FoundSpace**(`__namedParameters`): `Element` #### Parameters | Name | Type | | :------------------ | :---------- | | `__namedParameters` | `AlertIcon` | #### Returns `Element` --- ### FoundSpacetimeRip ▸ **FoundSpacetimeRip**(`__namedParameters`): `Element` #### Parameters | Name | Type | | :------------------ | :---------- | | `__namedParameters` | `AlertIcon` | #### Returns `Element` --- ### FoundSwamp ▸ **FoundSwamp**(`__namedParameters`): `Element` #### Parameters | Name | Type | | :------------------ | :---------- | | `__namedParameters` | `AlertIcon` | #### Returns `Element` --- ### FoundTradingPost ▸ **FoundTradingPost**(`__namedParameters`): `Element` #### Parameters | Name | Type | | :------------------ | :---------- | | `__namedParameters` | `AlertIcon` | #### Returns `Element` --- ### FoundTundra ▸ **FoundTundra**(`__namedParameters`): `Element` #### Parameters | Name | Type | | :------------------ | :---------- | | `__namedParameters` | `AlertIcon` | #### Returns `Element` --- ### FoundWasteland ▸ **FoundWasteland**(`__namedParameters`): `Element` #### Parameters | Name | Type | | :------------------ | :---------- | | `__namedParameters` | `AlertIcon` | #### Returns `Element` --- ### Generic ▸ **Generic**(`__namedParameters`): `Element` #### Parameters | Name | Type | | :------------------ | :---------- | | `__namedParameters` | `AlertIcon` | #### Returns `Element` --- ### MetPlayer ▸ **MetPlayer**(`__namedParameters`): `Element` #### Parameters | Name | Type | | :------------------ | :---------- | | `__namedParameters` | `AlertIcon` | #### Returns `Element` --- ### PlanetAttacked ▸ **PlanetAttacked**(`__namedParameters`): `Element` #### Parameters | Name | Type | | :------------------ | :---------- | | `__namedParameters` | `AlertIcon` | #### Returns `Element` --- ### PlanetConquered ▸ **PlanetConquered**(`__namedParameters`): `Element` #### Parameters | Name | Type | | :------------------ | :---------- | | `__namedParameters` | `AlertIcon` | #### Returns `Element` --- ### PlanetLost ▸ **PlanetLost**(`__namedParameters`): `Element` #### Parameters | Name | Type | | :------------------ | :---------- | | `__namedParameters` | `AlertIcon` | #### Returns `Element` --- ### Quasar ▸ **Quasar**(`__namedParameters`): `Element` #### Parameters | Name | Type | | :------------------ | :---------- | | `__namedParameters` | `AlertIcon` | #### Returns `Element` --- ### RankIcon ▸ **RankIcon**(`__namedParameters`): `Element` #### Parameters | Name | Type | | :------------------------- | :---------------------- | | `__namedParameters` | `Object` | | `__namedParameters.planet` | `undefined` \| `Planet` | #### Returns `Element` --- ### StatIcon ▸ **StatIcon**(`__namedParameters`): `Element` #### Parameters | Name | Type | | :----------------------- | :-------------------------------------------------------- | | `__namedParameters` | `Object` | | `__namedParameters.stat` | [`StatIdx`](../enums/types_global_GlobalTypes.StatIdx.md) | #### Returns `Element` --- ### TxAccepted ▸ **TxAccepted**(`__namedParameters`): `Element` #### Parameters | Name | Type | | :------------------ | :---------- | | `__namedParameters` | `AlertIcon` | #### Returns `Element` --- ### TxConfirmed ▸ **TxConfirmed**(`__namedParameters`): `Element` #### Parameters | Name | Type | | :------------------ | :---------- | | `__namedParameters` | `AlertIcon` | #### Returns `Element` --- ### TxDeclined ▸ **TxDeclined**(`__namedParameters`): `Element` #### Parameters | Name | Type | | :------------------ | :---------- | | `__namedParameters` | `AlertIcon` | #### Returns `Element` --- ### TxInitialized ▸ **TxInitialized**(`__namedParameters`): `Element` #### Parameters | Name | Type | | :------------------ | :---------- | | `__namedParameters` | `AlertIcon` | #### Returns `Element` ================================================ FILE: docs/modules/Frontend_Components_Input.md ================================================ # Module: Frontend/Components/Input ## Table of contents ### Classes - [DarkForestCheckbox](../classes/Frontend_Components_Input.DarkForestCheckbox.md) - [DarkForestColorInput](../classes/Frontend_Components_Input.DarkForestColorInput.md) - [DarkForestNumberInput](../classes/Frontend_Components_Input.DarkForestNumberInput.md) - [DarkForestTextInput](../classes/Frontend_Components_Input.DarkForestTextInput.md) ### Variables - [Checkbox](Frontend_Components_Input.md#checkbox) - [ColorInput](Frontend_Components_Input.md#colorinput) - [NumberInput](Frontend_Components_Input.md#numberinput) - [TextInput](Frontend_Components_Input.md#textinput) ## Variables ### Checkbox • `Const` **Checkbox**: `ForwardRefExoticComponent`<`Partial`<`Omit`<[`DarkForestCheckbox`](../classes/Frontend_Components_Input.DarkForestCheckbox.md), `"children"`\>\> & `Events`<{ `onChange`: (`e`: `Event` & `ChangeEvent`<[`DarkForestCheckbox`](../classes/Frontend_Components_Input.DarkForestCheckbox.md)\>) => `void` }\> & `HTMLAttributes`<`HTMLElement`\> & {} & `RefAttributes`<`unknown`\>\> --- ### ColorInput • `Const` **ColorInput**: `ForwardRefExoticComponent`<`Partial`<`Omit`<[`DarkForestColorInput`](../classes/Frontend_Components_Input.DarkForestColorInput.md), `"children"`\>\> & `Events`<{ `onChange`: (`e`: `Event` & `ChangeEvent`<[`DarkForestColorInput`](../classes/Frontend_Components_Input.DarkForestColorInput.md)\>) => `void` }\> & `HTMLAttributes`<`HTMLElement`\> & {} & `RefAttributes`<`unknown`\>\> --- ### NumberInput • `Const` **NumberInput**: `ForwardRefExoticComponent`<`Partial`<`Omit`<[`DarkForestNumberInput`](../classes/Frontend_Components_Input.DarkForestNumberInput.md), `"children"`\>\> & `Events`<{ `onChange`: (`e`: `Event` & `ChangeEvent`<[`DarkForestNumberInput`](../classes/Frontend_Components_Input.DarkForestNumberInput.md)\>) => `void` }\> & `HTMLAttributes`<`HTMLElement`\> & {} & `RefAttributes`<`unknown`\>\> --- ### TextInput • `Const` **TextInput**: `ForwardRefExoticComponent`<`Partial`<`Omit`<[`DarkForestTextInput`](../classes/Frontend_Components_Input.DarkForestTextInput.md), `"children"`\>\> & `Events`<{ `onBlur`: (`e`: `Event`) => `void` ; `onChange`: (`e`: `Event` & `ChangeEvent`<[`DarkForestTextInput`](../classes/Frontend_Components_Input.DarkForestTextInput.md)\>) => `void` }\> & `HTMLAttributes`<`HTMLElement`\> & {} & `RefAttributes`<`unknown`\>\> ================================================ FILE: docs/modules/Frontend_Components_Labels_ArtifactLabels.md ================================================ # Module: Frontend/Components/Labels/ArtifactLabels ## Table of contents ### Variables - [StyledArtifactRarityLabel](Frontend_Components_Labels_ArtifactLabels.md#styledartifactraritylabel) ### Functions - [ArtifactBiomeText](Frontend_Components_Labels_ArtifactLabels.md#artifactbiometext) - [ArtifactRarityBiomeTypeText](Frontend_Components_Labels_ArtifactLabels.md#artifactraritybiometypetext) - [ArtifactRarityLabel](Frontend_Components_Labels_ArtifactLabels.md#artifactraritylabel) - [ArtifactRarityLabelAnim](Frontend_Components_Labels_ArtifactLabels.md#artifactraritylabelanim) - [ArtifactRarityText](Frontend_Components_Labels_ArtifactLabels.md#artifactraritytext) - [ArtifactTypeText](Frontend_Components_Labels_ArtifactLabels.md#artifacttypetext) ## Variables ### StyledArtifactRarityLabel • `Const` **StyledArtifactRarityLabel**: `StyledComponent`<`"span"`, `any`, { `rarity`: `ArtifactRarity` }, `never`\> ## Functions ### ArtifactBiomeText ▸ **ArtifactBiomeText**(`__namedParameters`): `Element` #### Parameters | Name | Type | | :--------------------------- | :--------- | | `__namedParameters` | `Object` | | `__namedParameters.artifact` | `Artifact` | #### Returns `Element` --- ### ArtifactRarityBiomeTypeText ▸ **ArtifactRarityBiomeTypeText**(`__namedParameters`): `Element` #### Parameters | Name | Type | | :--------------------------- | :--------- | | `__namedParameters` | `Object` | | `__namedParameters.artifact` | `Artifact` | #### Returns `Element` --- ### ArtifactRarityLabel ▸ **ArtifactRarityLabel**(`__namedParameters`): `Element` #### Parameters | Name | Type | | :------------------------- | :--------------- | | `__namedParameters` | `Object` | | `__namedParameters.rarity` | `ArtifactRarity` | #### Returns `Element` --- ### ArtifactRarityLabelAnim ▸ **ArtifactRarityLabelAnim**(`__namedParameters`): `Element` #### Parameters | Name | Type | | :------------------------- | :--------------- | | `__namedParameters` | `Object` | | `__namedParameters.rarity` | `ArtifactRarity` | #### Returns `Element` --- ### ArtifactRarityText ▸ **ArtifactRarityText**(`__namedParameters`): `Element` #### Parameters | Name | Type | | :------------------------- | :--------------- | | `__namedParameters` | `Object` | | `__namedParameters.rarity` | `ArtifactRarity` | #### Returns `Element` --- ### ArtifactTypeText ▸ **ArtifactTypeText**(`__namedParameters`): `Element` #### Parameters | Name | Type | | :--------------------------- | :--------- | | `__namedParameters` | `Object` | | `__namedParameters.artifact` | `Artifact` | #### Returns `Element` ================================================ FILE: docs/modules/Frontend_Components_Labels_BiomeLabels.md ================================================ # Module: Frontend/Components/Labels/BiomeLabels ## Table of contents ### Variables - [BiomeLabel](Frontend_Components_Labels_BiomeLabels.md#biomelabel) ### Functions - [ArtifactBiomeLabel](Frontend_Components_Labels_BiomeLabels.md#artifactbiomelabel) - [ArtifactBiomeLabelAnim](Frontend_Components_Labels_BiomeLabels.md#artifactbiomelabelanim) - [BiomeLabelAnim](Frontend_Components_Labels_BiomeLabels.md#biomelabelanim) - [OptionalPlanetBiomeLabelAnim](Frontend_Components_Labels_BiomeLabels.md#optionalplanetbiomelabelanim) - [PlanetBiomeLabelAnim](Frontend_Components_Labels_BiomeLabels.md#planetbiomelabelanim) ## Variables ### BiomeLabel • `Const` **BiomeLabel**: `StyledComponent`<`"span"`, `any`, { `biome`: `Biome` }, `never`\> Renders colored text corresponding to a biome ## Functions ### ArtifactBiomeLabel ▸ **ArtifactBiomeLabel**(`__namedParameters`): `Element` #### Parameters | Name | Type | | :--------------------------- | :--------- | | `__namedParameters` | `Object` | | `__namedParameters.artifact` | `Artifact` | #### Returns `Element` --- ### ArtifactBiomeLabelAnim ▸ **ArtifactBiomeLabelAnim**(`__namedParameters`): `Element` #### Parameters | Name | Type | | :--------------------------- | :--------- | | `__namedParameters` | `Object` | | `__namedParameters.artifact` | `Artifact` | #### Returns `Element` --- ### BiomeLabelAnim ▸ **BiomeLabelAnim**(`__namedParameters`): `Element` Renders animated colored text corresponding to a biome #### Parameters | Name | Type | | :------------------------ | :------- | | `__namedParameters` | `Object` | | `__namedParameters.biome` | `Biome` | #### Returns `Element` --- ### OptionalPlanetBiomeLabelAnim ▸ **OptionalPlanetBiomeLabelAnim**(`__namedParameters`): `Element` #### Parameters | Name | Type | | :------------------------- | :---------------------- | | `__namedParameters` | `Object` | | `__namedParameters.planet` | `undefined` \| `Planet` | #### Returns `Element` --- ### PlanetBiomeLabelAnim ▸ **PlanetBiomeLabelAnim**(`__namedParameters`): `Element` #### Parameters | Name | Type | | :------------------------- | :---------------- | | `__namedParameters` | `Object` | | `__namedParameters.planet` | `LocatablePlanet` | #### Returns `Element` ================================================ FILE: docs/modules/Frontend_Components_Labels_KeywordLabels.md ================================================ # Module: Frontend/Components/Labels/KeywordLabels ## Table of contents ### Functions - [ScoreLabel](Frontend_Components_Labels_KeywordLabels.md#scorelabel) - [ScoreLabelTip](Frontend_Components_Labels_KeywordLabels.md#scorelabeltip) - [SilverLabel](Frontend_Components_Labels_KeywordLabels.md#silverlabel) - [SilverLabelTip](Frontend_Components_Labels_KeywordLabels.md#silverlabeltip) ## Functions ### ScoreLabel ▸ **ScoreLabel**(): `Element` #### Returns `Element` --- ### ScoreLabelTip ▸ **ScoreLabelTip**(): `Element` #### Returns `Element` --- ### SilverLabel ▸ **SilverLabel**(): `Element` #### Returns `Element` --- ### SilverLabelTip ▸ **SilverLabelTip**(): `Element` #### Returns `Element` ================================================ FILE: docs/modules/Frontend_Components_Labels_Labels.md ================================================ # Module: Frontend/Components/Labels/Labels ## Table of contents ### Functions - [AccountLabel](Frontend_Components_Labels_Labels.md#accountlabel) - [TwitterLink](Frontend_Components_Labels_Labels.md#twitterlink) ## Functions ### AccountLabel ▸ **AccountLabel**(`__namedParameters`): `Element` #### Parameters | Name | Type | | :---------------------------------------------- | :-------------- | | `__namedParameters` | `Object` | | `__namedParameters.ethAddress?` | `EthAddress` | | `__namedParameters.includeAddressIfHasTwitter?` | `boolean` | | `__namedParameters.style?` | `CSSProperties` | | `__namedParameters.width?` | `string` | #### Returns `Element` --- ### TwitterLink ▸ **TwitterLink**(`__namedParameters`): `Element` Link to a twitter account. #### Parameters | Name | Type | | :-------------------------- | :------- | | `__namedParameters` | `Object` | | `__namedParameters.color?` | `string` | | `__namedParameters.twitter` | `string` | #### Returns `Element` ================================================ FILE: docs/modules/Frontend_Components_Labels_LavaLabel.md ================================================ # Module: Frontend/Components/Labels/LavaLabel ## Table of contents ### Variables - [LavaLabel](Frontend_Components_Labels_LavaLabel.md#lavalabel) ### Functions - [LavaLabelRaw](Frontend_Components_Labels_LavaLabel.md#lavalabelraw) ## Variables ### LavaLabel • `Const` **LavaLabel**: `MemoExoticComponent`<() => `Element`\> ## Functions ### LavaLabelRaw ▸ **LavaLabelRaw**(): `Element` #### Returns `Element` ================================================ FILE: docs/modules/Frontend_Components_Labels_LegendaryLabel.md ================================================ # Module: Frontend/Components/Labels/LegendaryLabel ## Table of contents ### Variables - [LegendaryLabel](Frontend_Components_Labels_LegendaryLabel.md#legendarylabel) ## Variables ### LegendaryLabel • `Const` **LegendaryLabel**: `MemoExoticComponent`<() => `Element`\> ================================================ FILE: docs/modules/Frontend_Components_Labels_MythicLabel.md ================================================ # Module: Frontend/Components/Labels/MythicLabel ## Table of contents ### Variables - [MythicLabel](Frontend_Components_Labels_MythicLabel.md#mythiclabel) ### Functions - [MythicLabelText](Frontend_Components_Labels_MythicLabel.md#mythiclabeltext) ## Variables ### MythicLabel • `Const` **MythicLabel**: `MemoExoticComponent`<() => `Element`\> ## Functions ### MythicLabelText ▸ **MythicLabelText**(`__namedParameters`): `Element` #### Parameters | Name | Type | | :------------------------- | :-------------- | | `__namedParameters` | `Object` | | `__namedParameters.style?` | `CSSProperties` | | `__namedParameters.text` | `string` | #### Returns `Element` ================================================ FILE: docs/modules/Frontend_Components_Labels_PlanetLabels.md ================================================ # Module: Frontend/Components/Labels/PlanetLabels ## Table of contents ### Functions - [DefenseText](Frontend_Components_Labels_PlanetLabels.md#defensetext) - [EnergyCapText](Frontend_Components_Labels_PlanetLabels.md#energycaptext) - [EnergyGrowthText](Frontend_Components_Labels_PlanetLabels.md#energygrowthtext) - [EnergyText](Frontend_Components_Labels_PlanetLabels.md#energytext) - [GrowthText](Frontend_Components_Labels_PlanetLabels.md#growthtext) - [JunkText](Frontend_Components_Labels_PlanetLabels.md#junktext) - [LevelRankText](Frontend_Components_Labels_PlanetLabels.md#levelranktext) - [LevelRankTextEm](Frontend_Components_Labels_PlanetLabels.md#levelranktextem) - [PlanetBiomeTypeLabelAnim](Frontend_Components_Labels_PlanetLabels.md#planetbiometypelabelanim) - [PlanetEnergyLabel](Frontend_Components_Labels_PlanetLabels.md#planetenergylabel) - [PlanetLevel](Frontend_Components_Labels_PlanetLabels.md#planetlevel) - [PlanetLevelText](Frontend_Components_Labels_PlanetLabels.md#planetleveltext) - [PlanetOwnerLabel](Frontend_Components_Labels_PlanetLabels.md#planetownerlabel) - [PlanetRank](Frontend_Components_Labels_PlanetLabels.md#planetrank) - [PlanetRankText](Frontend_Components_Labels_PlanetLabels.md#planetranktext) - [PlanetSilverLabel](Frontend_Components_Labels_PlanetLabels.md#planetsilverlabel) - [PlanetTypeLabelAnim](Frontend_Components_Labels_PlanetLabels.md#planettypelabelanim) - [RangeText](Frontend_Components_Labels_PlanetLabels.md#rangetext) - [SilverCapText](Frontend_Components_Labels_PlanetLabels.md#silvercaptext) - [SilverGrowthText](Frontend_Components_Labels_PlanetLabels.md#silvergrowthtext) - [SilverText](Frontend_Components_Labels_PlanetLabels.md#silvertext) - [SpeedText](Frontend_Components_Labels_PlanetLabels.md#speedtext) - [StatText](Frontend_Components_Labels_PlanetLabels.md#stattext) ## Functions ### DefenseText ▸ **DefenseText**(`__namedParameters`): `Element` #### Parameters | Name | Type | | :------------------------- | :---------------------- | | `__namedParameters` | `Object` | | `__namedParameters.planet` | `undefined` \| `Planet` | #### Returns `Element` --- ### EnergyCapText ▸ **EnergyCapText**(`__namedParameters`): `Element` #### Parameters | Name | Type | | :------------------------- | :---------------------- | | `__namedParameters` | `Object` | | `__namedParameters.planet` | `undefined` \| `Planet` | #### Returns `Element` --- ### EnergyGrowthText ▸ **EnergyGrowthText**(`__namedParameters`): `Element` #### Parameters | Name | Type | | :------------------------- | :---------------------- | | `__namedParameters` | `Object` | | `__namedParameters.planet` | `undefined` \| `Planet` | #### Returns `Element` --- ### EnergyText ▸ **EnergyText**(`__namedParameters`): `Element` #### Parameters | Name | Type | | :------------------------- | :---------------------- | | `__namedParameters` | `Object` | | `__namedParameters.planet` | `undefined` \| `Planet` | #### Returns `Element` --- ### GrowthText ▸ **GrowthText**(`__namedParameters`): `Element` #### Parameters | Name | Type | | :-------------------------- | :-------------------------- | | `__namedParameters` | `Object` | | `__namedParameters.planet` | `undefined` \| `Planet` | | `__namedParameters.style?` | `CSSProperties` | | `__namedParameters.getStat` | (`p`: `Planet`) => `number` | #### Returns `Element` --- ### JunkText ▸ **JunkText**(`__namedParameters`): `Element` #### Parameters | Name | Type | | :------------------------- | :---------------------- | | `__namedParameters` | `Object` | | `__namedParameters.planet` | `undefined` \| `Planet` | #### Returns `Element` --- ### LevelRankText ▸ **LevelRankText**(`__namedParameters`): `Element` #### Parameters | Name | Type | | :------------------------- | :---------------------- | | `__namedParameters` | `Object` | | `__namedParameters.delim?` | `string` | | `__namedParameters.planet` | `undefined` \| `Planet` | #### Returns `Element` --- ### LevelRankTextEm ▸ **LevelRankTextEm**(`__namedParameters`): `Element` #### Parameters | Name | Type | | :------------------------- | :---------------------- | | `__namedParameters` | `Object` | | `__namedParameters.delim?` | `string` | | `__namedParameters.planet` | `undefined` \| `Planet` | #### Returns `Element` --- ### PlanetBiomeTypeLabelAnim ▸ **PlanetBiomeTypeLabelAnim**(`__namedParameters`): `Element` #### Parameters | Name | Type | | :------------------------- | :---------------------- | | `__namedParameters` | `Object` | | `__namedParameters.planet` | `undefined` \| `Planet` | #### Returns `Element` --- ### PlanetEnergyLabel ▸ **PlanetEnergyLabel**(`__namedParameters`): `Element` #### Parameters | Name | Type | | :------------------------- | :---------------------- | | `__namedParameters` | `Object` | | `__namedParameters.planet` | `undefined` \| `Planet` | #### Returns `Element` --- ### PlanetLevel ▸ **PlanetLevel**(`__namedParameters`): `Element` #### Parameters | Name | Type | | :------------------------- | :---------------------- | | `__namedParameters` | `Object` | | `__namedParameters.planet` | `undefined` \| `Planet` | #### Returns `Element` --- ### PlanetLevelText ▸ **PlanetLevelText**(`__namedParameters`): `Element` #### Parameters | Name | Type | | :------------------------- | :---------------------- | | `__namedParameters` | `Object` | | `__namedParameters.planet` | `undefined` \| `Planet` | #### Returns `Element` --- ### PlanetOwnerLabel ▸ **PlanetOwnerLabel**(`__namedParameters`): `Element` Either 'yours' in green text, #### Parameters | Name | Type | | :---------------------------------------- | :---------------------- | | `__namedParameters` | `Object` | | `__namedParameters.abbreviateOwnAddress?` | `boolean` | | `__namedParameters.colorWithOwnerColor?` | `boolean` | | `__namedParameters.planet` | `undefined` \| `Planet` | #### Returns `Element` --- ### PlanetRank ▸ **PlanetRank**(`__namedParameters`): `Element` #### Parameters | Name | Type | | :------------------------- | :---------------------- | | `__namedParameters` | `Object` | | `__namedParameters.planet` | `undefined` \| `Planet` | #### Returns `Element` --- ### PlanetRankText ▸ **PlanetRankText**(`__namedParameters`): `Element` #### Parameters | Name | Type | | :------------------------- | :---------------------- | | `__namedParameters` | `Object` | | `__namedParameters.planet` | `undefined` \| `Planet` | #### Returns `Element` --- ### PlanetSilverLabel ▸ **PlanetSilverLabel**(`__namedParameters`): `Element` #### Parameters | Name | Type | | :------------------------- | :---------------------- | | `__namedParameters` | `Object` | | `__namedParameters.planet` | `undefined` \| `Planet` | #### Returns `Element` --- ### PlanetTypeLabelAnim ▸ **PlanetTypeLabelAnim**(`__namedParameters`): `Element` #### Parameters | Name | Type | | :------------------------- | :---------------------- | | `__namedParameters` | `Object` | | `__namedParameters.planet` | `undefined` \| `Planet` | #### Returns `Element` --- ### RangeText ▸ **RangeText**(`__namedParameters`): `Element` #### Parameters | Name | Type | | :------------------------- | :---------------------- | | `__namedParameters` | `Object` | | `__namedParameters.buff?` | `number` | | `__namedParameters.planet` | `undefined` \| `Planet` | #### Returns `Element` --- ### SilverCapText ▸ **SilverCapText**(`__namedParameters`): `Element` #### Parameters | Name | Type | | :------------------------- | :---------------------- | | `__namedParameters` | `Object` | | `__namedParameters.planet` | `undefined` \| `Planet` | #### Returns `Element` --- ### SilverGrowthText ▸ **SilverGrowthText**(`__namedParameters`): `Element` #### Parameters | Name | Type | | :------------------------- | :---------------------- | | `__namedParameters` | `Object` | | `__namedParameters.planet` | `undefined` \| `Planet` | #### Returns `Element` --- ### SilverText ▸ **SilverText**(`__namedParameters`): `Element` #### Parameters | Name | Type | | :------------------------- | :---------------------- | | `__namedParameters` | `Object` | | `__namedParameters.planet` | `undefined` \| `Planet` | #### Returns `Element` --- ### SpeedText ▸ **SpeedText**(`__namedParameters`): `Element` #### Parameters | Name | Type | | :------------------------- | :---------------------- | | `__namedParameters` | `Object` | | `__namedParameters.buff?` | `number` | | `__namedParameters.planet` | `undefined` \| `Planet` | #### Returns `Element` --- ### StatText ▸ **StatText**(`__namedParameters`): `Element` #### Parameters | Name | Type | | :-------------------------- | :-------------------------- | | `__namedParameters` | `Object` | | `__namedParameters.buff?` | `number` | | `__namedParameters.planet` | `undefined` \| `Planet` | | `__namedParameters.style?` | `CSSProperties` | | `__namedParameters.getStat` | (`p`: `Planet`) => `number` | #### Returns `Element` ================================================ FILE: docs/modules/Frontend_Components_Labels_SpacetimeRipLabel.md ================================================ # Module: Frontend/Components/Labels/SpacetimeRipLabel ## Table of contents ### Variables - [SpacetimeRipLabel](Frontend_Components_Labels_SpacetimeRipLabel.md#spacetimeriplabel) ## Variables ### SpacetimeRipLabel • `Const` **SpacetimeRipLabel**: `MemoExoticComponent`<() => `Element`\> ================================================ FILE: docs/modules/Frontend_Components_Labels_WastelandLabel.md ================================================ # Module: Frontend/Components/Labels/WastelandLabel ## Table of contents ### Variables - [WastelandLabel](Frontend_Components_Labels_WastelandLabel.md#wastelandlabel) ### Functions - [WastelandLabelRaw](Frontend_Components_Labels_WastelandLabel.md#wastelandlabelraw) ## Variables ### WastelandLabel • `Const` **WastelandLabel**: `MemoExoticComponent`<() => `Element`\> ## Functions ### WastelandLabelRaw ▸ **WastelandLabelRaw**(): `Element` #### Returns `Element` ================================================ FILE: docs/modules/Frontend_Components_LoadingSpinner.md ================================================ # Module: Frontend/Components/LoadingSpinner ## Table of contents ### Functions - [LoadingSpinner](Frontend_Components_LoadingSpinner.md#loadingspinner) ## Functions ### LoadingSpinner ▸ **LoadingSpinner**(`__namedParameters`): `Element` #### Parameters | Name | Type | | :------------------------------- | :------- | | `__namedParameters` | `Object` | | `__namedParameters.initialText?` | `string` | | `__namedParameters.rate?` | `number` | #### Returns `Element` ================================================ FILE: docs/modules/Frontend_Components_MaybeShortcutButton.md ================================================ # Module: Frontend/Components/MaybeShortcutButton ## Table of contents ### Functions - [MaybeShortcutButton](Frontend_Components_MaybeShortcutButton.md#maybeshortcutbutton) ## Functions ### MaybeShortcutButton ▸ **MaybeShortcutButton**(`props`): `Element` A button that will show shortcuts if enabled globally in the game, otherwise it will display a normal button Must ONLY be used when a GameUIManager is available. #### Parameters | Name | Type | | :------ | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `props` | `Partial`<`Omit`<[`DarkForestButton`](../classes/Frontend_Components_Btn.DarkForestButton.md), `"children"`\>\> & `Events`<{ `onClick`: (`evt`: `Event` & `MouseEvent`<[`DarkForestButton`](../classes/Frontend_Components_Btn.DarkForestButton.md), `MouseEvent`\>) => `void` }\> & `HTMLAttributes`<`HTMLElement`\> & {} & `RefAttributes`<`unknown`\> \| `Partial`<`Omit`<[`DarkForestShortcutButton`](../classes/Frontend_Components_Btn.DarkForestShortcutButton.md), `"children"`\>\> & `Events`<{ `onClick`: (`evt`: `Event` & `MouseEvent`<[`DarkForestShortcutButton`](../classes/Frontend_Components_Btn.DarkForestShortcutButton.md), `MouseEvent`\>) => `void` ; `onShortcutPressed`: (`evt`: [`ShortcutPressedEvent`](../classes/Frontend_Components_Btn.ShortcutPressedEvent.md)) => `void` }\> & `HTMLAttributes`<`HTMLElement`\> & {} & `RefAttributes`<`unknown`\> | #### Returns `Element` ================================================ FILE: docs/modules/Frontend_Components_MineArtifactButton.md ================================================ # Module: Frontend/Components/MineArtifactButton ## Table of contents ### Functions - [MineArtifactButton](Frontend_Components_MineArtifactButton.md#mineartifactbutton) ## Functions ### MineArtifactButton ▸ **MineArtifactButton**(`__namedParameters`): `Element` #### Parameters | Name | Type | | :-------------------------------- | :--------------------------------------------------------------------------------- | | `__namedParameters` | `Object` | | `__namedParameters.planetWrapper` | [`Wrapper`](../classes/Backend_Utils_Wrapper.Wrapper.md)<`undefined` \| `Planet`\> | #### Returns `Element` ================================================ FILE: docs/modules/Frontend_Components_Modal.md ================================================ # Module: Frontend/Components/Modal ## Table of contents ### Classes - [DarkForestModal](../classes/Frontend_Components_Modal.DarkForestModal.md) - [PositionChangedEvent](../classes/Frontend_Components_Modal.PositionChangedEvent.md) ### Variables - [Modal](Frontend_Components_Modal.md#modal) ## Variables ### Modal • `Const` **Modal**: `ForwardRefExoticComponent`<`Partial`<`Omit`<[`DarkForestModal`](../classes/Frontend_Components_Modal.DarkForestModal.md), `"children"`\>\> & `Events`<{ `onMouseDown`: (`evt`: `Event` & `MouseEvent`<[`DarkForestModal`](../classes/Frontend_Components_Modal.DarkForestModal.md), `MouseEvent`\>) => `void` ; `onPositionChanged`: (`evt`: [`PositionChangedEvent`](../classes/Frontend_Components_Modal.PositionChangedEvent.md)) => `void` }\> & `HTMLAttributes`<`HTMLElement`\> & {} & `RefAttributes`<`unknown`\>\> ================================================ FILE: docs/modules/Frontend_Components_OpenPaneButtons.md ================================================ # Module: Frontend/Components/OpenPaneButtons ## Table of contents ### Functions - [OpenBroadcastPaneButton](Frontend_Components_OpenPaneButtons.md#openbroadcastpanebutton) - [OpenHatPaneButton](Frontend_Components_OpenPaneButtons.md#openhatpanebutton) - [OpenManagePlanetArtifactsButton](Frontend_Components_OpenPaneButtons.md#openmanageplanetartifactsbutton) - [OpenPaneButton](Frontend_Components_OpenPaneButtons.md#openpanebutton) - [OpenPlanetInfoButton](Frontend_Components_OpenPaneButtons.md#openplanetinfobutton) - [OpenUpgradeDetailsPaneButton](Frontend_Components_OpenPaneButtons.md#openupgradedetailspanebutton) ## Functions ### OpenBroadcastPaneButton ▸ **OpenBroadcastPaneButton**(`__namedParameters`): `Element` #### Parameters | Name | Type | | :--------------------------- | :--------------------------------------------------------------------- | | `__namedParameters` | `Object` | | `__namedParameters.modal` | [`ModalHandle`](../interfaces/Frontend_Views_ModalPane.ModalHandle.md) | | `__namedParameters.planetId` | `undefined` \| `LocationId` | #### Returns `Element` --- ### OpenHatPaneButton ▸ **OpenHatPaneButton**(`__namedParameters`): `Element` #### Parameters | Name | Type | | :--------------------------- | :--------------------------------------------------------------------- | | `__namedParameters` | `Object` | | `__namedParameters.modal` | [`ModalHandle`](../interfaces/Frontend_Views_ModalPane.ModalHandle.md) | | `__namedParameters.planetId` | `undefined` \| `LocationId` | #### Returns `Element` --- ### OpenManagePlanetArtifactsButton ▸ **OpenManagePlanetArtifactsButton**(`__namedParameters`): `Element` #### Parameters | Name | Type | | :--------------------------- | :--------------------------------------------------------------------- | | `__namedParameters` | `Object` | | `__namedParameters.modal` | [`ModalHandle`](../interfaces/Frontend_Views_ModalPane.ModalHandle.md) | | `__namedParameters.planetId` | `undefined` \| `LocationId` | #### Returns `Element` --- ### OpenPaneButton ▸ **OpenPaneButton**(`__namedParameters`): `Element` #### Parameters | Name | Type | | :------------------------------- | :------------------------------------------------------------------------ | | `__namedParameters` | `Object` | | `__namedParameters.helpContent?` | `ReactElement`<`any`, `string` \| `JSXElementConstructor`<`any`\>\> | | `__namedParameters.modal` | [`ModalHandle`](../interfaces/Frontend_Views_ModalPane.ModalHandle.md) | | `__namedParameters.shortcut?` | `string` | | `__namedParameters.title` | `string` | | `__namedParameters.element` | () => `ReactElement`<`any`, `string` \| `JSXElementConstructor`<`any`\>\> | #### Returns `Element` --- ### OpenPlanetInfoButton ▸ **OpenPlanetInfoButton**(`__namedParameters`): `Element` #### Parameters | Name | Type | | :--------------------------- | :--------------------------------------------------------------------- | | `__namedParameters` | `Object` | | `__namedParameters.modal` | [`ModalHandle`](../interfaces/Frontend_Views_ModalPane.ModalHandle.md) | | `__namedParameters.planetId` | `undefined` \| `LocationId` | #### Returns `Element` --- ### OpenUpgradeDetailsPaneButton ▸ **OpenUpgradeDetailsPaneButton**(`__namedParameters`): `Element` #### Parameters | Name | Type | | :--------------------------- | :--------------------------------------------------------------------- | | `__namedParameters` | `Object` | | `__namedParameters.modal` | [`ModalHandle`](../interfaces/Frontend_Views_ModalPane.ModalHandle.md) | | `__namedParameters.planetId` | `undefined` \| `LocationId` | #### Returns `Element` ================================================ FILE: docs/modules/Frontend_Components_PluginModal.md ================================================ # Module: Frontend/Components/PluginModal ## Table of contents ### Functions - [PluginModal](Frontend_Components_PluginModal.md#pluginmodal) ## Functions ### PluginModal ▸ **PluginModal**(`__namedParameters`): `ReactPortal` #### Parameters | Name | Type | | :---------------------------- | :--------------------------------- | | `__namedParameters` | `Object` | | `__namedParameters.container` | `Element` | | `__namedParameters.id` | `ModalId` | | `__namedParameters.title` | `string` | | `__namedParameters.width?` | `string` | | `__namedParameters.onClose` | () => `void` | | `__namedParameters.onRender` | (`el`: `HTMLDivElement`) => `void` | #### Returns `ReactPortal` ================================================ FILE: docs/modules/Frontend_Components_ReadMore.md ================================================ # Module: Frontend/Components/ReadMore ## Table of contents ### Functions - [ReadMore](Frontend_Components_ReadMore.md#readmore) ## Functions ### ReadMore ▸ **ReadMore**(`__namedParameters`): `Element` #### Parameters | Name | Type | | :-------------------------------------- | :--------------------------- | | `__namedParameters` | `Object` | | `__namedParameters.children` | `ReactNode` \| `ReactNode`[] | | `__namedParameters.height?` | `string` | | `__namedParameters.text?` | `string` | | `__namedParameters.toggleButtonMargin?` | `string` | #### Returns `Element` ================================================ FILE: docs/modules/Frontend_Components_RemoteModal.md ================================================ # Module: Frontend/Components/RemoteModal ## Table of contents ### Functions - [RemoteModal](Frontend_Components_RemoteModal.md#remotemodal) ## Functions ### RemoteModal ▸ **RemoteModal**(`__namedParameters`): `ReactPortal` Allows you to instantiate a modal, and render it into the desired element. Useful for loading temporary modals from ANYWHERE in the UI, not just [GameWindowLayout](Frontend_Views_GameWindowLayout.md#gamewindowlayout) #### Parameters | Name | Type | | :------------------ | :----------------------------------------------------------------------------------------------------------------------------------------------------------- | | `__namedParameters` | `PropsWithChildren`<{ `container`: `Element` ; `id`: `ModalId` ; `title`: `string` ; `visible`: `boolean` ; `width?`: `string` ; `onClose`: () => `void` }\> | #### Returns `ReactPortal` ================================================ FILE: docs/modules/Frontend_Components_Row.md ================================================ # Module: Frontend/Components/Row ## Table of contents ### Variables - [Row](Frontend_Components_Row.md#row) ## Variables ### Row • `Const` **Row**: `ForwardRefExoticComponent`<`Partial`<`Omit`<`DarkForestRow`, `"children"`\>\> & `Events`<`unknown`\> & `HTMLAttributes`<`HTMLElement`\> & {} & `RefAttributes`<`unknown`\>\> ================================================ FILE: docs/modules/Frontend_Components_Slider.md ================================================ # Module: Frontend/Components/Slider ## Table of contents ### Classes - [DarkForestSlider](../classes/Frontend_Components_Slider.DarkForestSlider.md) - [DarkForestSliderHandle](../classes/Frontend_Components_Slider.DarkForestSliderHandle.md) ### Variables - [Slider](Frontend_Components_Slider.md#slider) - [SliderHandle](Frontend_Components_Slider.md#sliderhandle) ## Variables ### Slider • `Const` **Slider**: `ForwardRefExoticComponent`<`Partial`<`Omit`<[`DarkForestSlider`](../classes/Frontend_Components_Slider.DarkForestSlider.md), `"children"`\>\> & `Events`<{ `onChange`: (`e`: `Event` & `ChangeEvent`<[`DarkForestSlider`](../classes/Frontend_Components_Slider.DarkForestSlider.md)\>) => `void` }\> & `HTMLAttributes`<`HTMLElement`\> & {} & `RefAttributes`<`unknown`\>\> --- ### SliderHandle • `Const` **SliderHandle**: `ForwardRefExoticComponent`<`Partial`<`Omit`<[`DarkForestSliderHandle`](../classes/Frontend_Components_Slider.DarkForestSliderHandle.md), `"children"`\>\> & `Events`<{ `onChange`: (`e`: `Event` & `ChangeEvent`<[`DarkForestSliderHandle`](../classes/Frontend_Components_Slider.DarkForestSliderHandle.md)\>) => `void` }\> & `HTMLAttributes`<`HTMLElement`\> & {} & `RefAttributes`<`unknown`\>\> ================================================ FILE: docs/modules/Frontend_Components_Text.md ================================================ # Module: Frontend/Components/Text ## Table of contents ### Variables - [Blue](Frontend_Components_Text.md#blue) - [Colored](Frontend_Components_Text.md#colored) - [Gold](Frontend_Components_Text.md#gold) - [Green](Frontend_Components_Text.md#green) - [HideSmall](Frontend_Components_Text.md#hidesmall) - [Invisible](Frontend_Components_Text.md#invisible) - [Red](Frontend_Components_Text.md#red) - [Smaller](Frontend_Components_Text.md#smaller) - [Sub](Frontend_Components_Text.md#sub) - [Subber](Frontend_Components_Text.md#subber) - [Text](Frontend_Components_Text.md#text) - [White](Frontend_Components_Text.md#white) ### Functions - [ArtifactNameLink](Frontend_Components_Text.md#artifactnamelink) - [BlinkCursor](Frontend_Components_Text.md#blinkcursor) - [CenterChunkLink](Frontend_Components_Text.md#centerchunklink) - [CenterPlanetLink](Frontend_Components_Text.md#centerplanetlink) - [Coords](Frontend_Components_Text.md#coords) - [FAQ04Link](Frontend_Components_Text.md#faq04link) - [LongDash](Frontend_Components_Text.md#longdash) - [PlanetNameLink](Frontend_Components_Text.md#planetnamelink) - [TxLink](Frontend_Components_Text.md#txlink) ## Variables ### Blue • `Const` **Blue**: `StyledComponent`<`"span"`, `any`, {}, `never`\> --- ### Colored • `Const` **Colored**: `StyledComponent`<`"span"`, `any`, { `color`: `string` }, `never`\> --- ### Gold • `Const` **Gold**: `StyledComponent`<`"span"`, `any`, {}, `never`\> --- ### Green • `Const` **Green**: `StyledComponent`<`"span"`, `any`, {}, `never`\> --- ### HideSmall • `Const` **HideSmall**: `StyledComponent`<`"span"`, `any`, {}, `never`\> --- ### Invisible • `Const` **Invisible**: `StyledComponent`<`"span"`, `any`, {}, `never`\> --- ### Red • `Const` **Red**: `StyledComponent`<`"span"`, `any`, {}, `never`\> --- ### Smaller • `Const` **Smaller**: `StyledComponent`<`"span"`, `any`, {}, `never`\> --- ### Sub • `Const` **Sub**: `StyledComponent`<`"span"`, `any`, {}, `never`\> --- ### Subber • `Const` **Subber**: `StyledComponent`<`"span"`, `any`, {}, `never`\> --- ### Text • `Const` **Text**: `StyledComponent`<`"span"`, `any`, {}, `never`\> --- ### White • `Const` **White**: `StyledComponent`<`"span"`, `any`, {}, `never`\> ## Functions ### ArtifactNameLink ▸ **ArtifactNameLink**(`__namedParameters`): `Element` #### Parameters | Name | Type | | :--------------------- | :----------- | | `__namedParameters` | `Object` | | `__namedParameters.id` | `ArtifactId` | #### Returns `Element` --- ### BlinkCursor ▸ **BlinkCursor**(): `Element` #### Returns `Element` --- ### CenterChunkLink ▸ **CenterChunkLink**(`__namedParameters`): `Element` #### Parameters | Name | Type | | :--------------------------- | :---------- | | `__namedParameters` | `Object` | | `__namedParameters.children` | `ReactNode` | | `__namedParameters.chunk` | `Chunk` | #### Returns `Element` --- ### CenterPlanetLink ▸ **CenterPlanetLink**(`__namedParameters`): `Element` #### Parameters | Name | Type | | :--------------------------- | :---------- | | `__namedParameters` | `Object` | | `__namedParameters.children` | `ReactNode` | | `__namedParameters.planet` | `Planet` | #### Returns `Element` --- ### Coords ▸ **Coords**(`__namedParameters`): `Element` #### Parameters | Name | Type | | :------------------------- | :------------ | | `__namedParameters` | `Object` | | `__namedParameters.coords` | `WorldCoords` | #### Returns `Element` --- ### FAQ04Link ▸ **FAQ04Link**(`__namedParameters`): `Element` #### Parameters | Name | Type | | :--------------------------- | :---------- | | `__namedParameters` | `Object` | | `__namedParameters.children` | `ReactNode` | #### Returns `Element` --- ### LongDash ▸ **LongDash**(): `Element` #### Returns `Element` --- ### PlanetNameLink ▸ **PlanetNameLink**(`__namedParameters`): `Element` #### Parameters | Name | Type | | :------------------------- | :------- | | `__namedParameters` | `Object` | | `__namedParameters.planet` | `Planet` | #### Returns `Element` --- ### TxLink ▸ **TxLink**(`__namedParameters`): `Element` #### Parameters | Name | Type | | :--------------------- | :------------------------- | | `__namedParameters` | `Object` | | `__namedParameters.tx` | `Transaction`<`TxIntent`\> | #### Returns `Element` ================================================ FILE: docs/modules/Frontend_Components_TextLoadingBar.md ================================================ # Module: Frontend/Components/TextLoadingBar ## Table of contents ### Interfaces - [LoadingBarHandle](../interfaces/Frontend_Components_TextLoadingBar.LoadingBarHandle.md) ### Variables - [TextLoadingBar](Frontend_Components_TextLoadingBar.md#textloadingbar) ### Functions - [TextLoadingBarImpl](Frontend_Components_TextLoadingBar.md#textloadingbarimpl) ## Variables ### TextLoadingBar • `Const` **TextLoadingBar**: `ForwardRefExoticComponent`<`LoadingBarProps` & `RefAttributes`<`undefined` \| [`LoadingBarHandle`](../interfaces/Frontend_Components_TextLoadingBar.LoadingBarHandle.md)\>\> ## Functions ### TextLoadingBarImpl ▸ **TextLoadingBarImpl**(`__namedParameters`, `ref`): `Element` #### Parameters | Name | Type | | :------------------ | :------------------------------------------------------------------------------------------------- | | `__namedParameters` | `LoadingBarProps` | | `ref` | `Ref`<[`LoadingBarHandle`](../interfaces/Frontend_Components_TextLoadingBar.LoadingBarHandle.md)\> | #### Returns `Element` ================================================ FILE: docs/modules/Frontend_Components_TextPreview.md ================================================ # Module: Frontend/Components/TextPreview ## Table of contents ### Functions - [TextPreview](Frontend_Components_TextPreview.md#textpreview) ## Functions ### TextPreview ▸ **TextPreview**(`__namedParameters`): `Element` #### Parameters | Name | Type | | :---------------------------------- | :-------------- | | `__namedParameters` | `Object` | | `__namedParameters.focusedWidth?` | `string` | | `__namedParameters.maxLength?` | `number` | | `__namedParameters.style?` | `CSSProperties` | | `__namedParameters.text?` | `string` | | `__namedParameters.unFocusedWidth?` | `string` | #### Returns `Element` ================================================ FILE: docs/modules/Frontend_Components_Theme.md ================================================ # Module: Frontend/Components/Theme ## Table of contents ### Variables - [Theme](Frontend_Components_Theme.md#theme) ## Variables ### Theme • `Const` **Theme**: `ForwardRefExoticComponent`<`Partial`<`Omit`<`DarkForestTheme`, `"children"`\>\> & `Events`<`unknown`\> & `HTMLAttributes`<`HTMLElement`\> & {} & `RefAttributes`<`unknown`\>\> ================================================ FILE: docs/modules/Frontend_Components_TimeUntil.md ================================================ # Module: Frontend/Components/TimeUntil ## Table of contents ### Functions - [TimeUntil](Frontend_Components_TimeUntil.md#timeuntil) - [formatDuration](Frontend_Components_TimeUntil.md#formatduration) ## Functions ### TimeUntil ▸ **TimeUntil**(`__namedParameters`): `Element` Given a timestamp, displays the amount of time until the timestamp from now in hh:mm:ss format. If the timestamp is in the past, displays the given hardcoded value. #### Parameters | Name | Type | | :---------------------------- | :------- | | `__namedParameters` | `Object` | | `__namedParameters.ifPassed` | `string` | | `__namedParameters.timestamp` | `number` | #### Returns `Element` --- ### formatDuration ▸ **formatDuration**(`msDuration`): `string` #### Parameters | Name | Type | | :----------- | :------- | | `msDuration` | `number` | #### Returns `string` ================================================ FILE: docs/modules/Frontend_Game_ControllableCanvas.md ================================================ # Module: Frontend/Game/ControllableCanvas ## Table of contents ### Functions - [default](Frontend_Game_ControllableCanvas.md#default) ## Functions ### default ▸ **default**(): `Element` #### Returns `Element` ================================================ FILE: docs/modules/Frontend_Game_ModalManager.md ================================================ # Module: Frontend/Game/ModalManager ## Table of contents ### Classes - [default](../classes/Frontend_Game_ModalManager.default.md) ================================================ FILE: docs/modules/Frontend_Game_NotificationManager.md ================================================ # Module: Frontend/Game/NotificationManager ## Table of contents ### Enumerations - [NotificationManagerEvent](../enums/Frontend_Game_NotificationManager.NotificationManagerEvent.md) - [NotificationType](../enums/Frontend_Game_NotificationManager.NotificationType.md) ### Classes - [default](../classes/Frontend_Game_NotificationManager.default.md) ### Type aliases - [NotificationInfo](Frontend_Game_NotificationManager.md#notificationinfo) ## Type aliases ### NotificationInfo Ƭ **NotificationInfo**: `Object` #### Type declaration | Name | Type | | :---------- | :----------------------------------------------------------------------------------- | | `color?` | `string` | | `icon` | `React.ReactNode` | | `id` | `string` | | `message` | `React.ReactNode` | | `txData?` | `TxIntent` | | `txStatus?` | `EthTxStatus` | | `type` | [`NotificationType`](../enums/Frontend_Game_NotificationManager.NotificationType.md) | ================================================ FILE: docs/modules/Frontend_Game_Popups.md ================================================ # Module: Frontend/Game/Popups ## Table of contents ### Functions - [openConfirmationWindowForTransaction](Frontend_Game_Popups.md#openconfirmationwindowfortransaction) ## Functions ### openConfirmationWindowForTransaction ▸ **openConfirmationWindowForTransaction**(`__namedParameters`): `Promise`<`void`\> #### Parameters | Name | Type | | :------------------ | :----------------------- | | `__namedParameters` | `OpenConfirmationConfig` | #### Returns `Promise`<`void`\> ================================================ FILE: docs/modules/Frontend_Game_Viewport.md ================================================ # Module: Frontend/Game/Viewport ## Table of contents ### Classes - [default](../classes/Frontend_Game_Viewport.default.md) ### Functions - [getDefaultScroll](Frontend_Game_Viewport.md#getdefaultscroll) ## Functions ### getDefaultScroll ▸ **getDefaultScroll**(): `number` #### Returns `number` ================================================ FILE: docs/modules/Frontend_Pages_App.md ================================================ # Module: Frontend/Pages/App ## Table of contents ### Functions - [default](Frontend_Pages_App.md#default) ## Functions ### default ▸ **default**(): `Element` #### Returns `Element` ================================================ FILE: docs/modules/Frontend_Pages_CreateLobby.md ================================================ # Module: Frontend/Pages/CreateLobby ## Table of contents ### Functions - [CreateLobby](Frontend_Pages_CreateLobby.md#createlobby) ## Functions ### CreateLobby ▸ **CreateLobby**(`__namedParameters`): `Element` #### Parameters | Name | Type | | :------------------ | :--------------------------------------------------------------------------- | | `__namedParameters` | `RouteComponentProps`<{ `contract`: `string` }, `StaticContext`, `unknown`\> | #### Returns `Element` ================================================ FILE: docs/modules/Frontend_Pages_EventsPage.md ================================================ # Module: Frontend/Pages/EventsPage ## Table of contents ### Functions - [EventsPage](Frontend_Pages_EventsPage.md#eventspage) ## Functions ### EventsPage ▸ **EventsPage**(): `Element` #### Returns `Element` ================================================ FILE: docs/modules/Frontend_Pages_GameLandingPage.md ================================================ # Module: Frontend/Pages/GameLandingPage ## Table of contents ### Functions - [GameLandingPage](Frontend_Pages_GameLandingPage.md#gamelandingpage) ## Functions ### GameLandingPage ▸ **GameLandingPage**(`__namedParameters`): `Element` #### Parameters | Name | Type | | :------------------ | :--------------------------------------------------------------------------- | | `__namedParameters` | `RouteComponentProps`<{ `contract`: `string` }, `StaticContext`, `unknown`\> | #### Returns `Element` ================================================ FILE: docs/modules/Frontend_Pages_GifMaker.md ================================================ # Module: Frontend/Pages/GifMaker ## Table of contents ### Variables - [GIF_ARTIFACT_COLOR](Frontend_Pages_GifMaker.md#gif_artifact_color) ### Functions - [GifMaker](Frontend_Pages_GifMaker.md#gifmaker) ## Variables ### GIF_ARTIFACT_COLOR • `Const` **GIF_ARTIFACT_COLOR**: `ArtifactFileColor` = `ArtifactFileColor.APP_BACKGROUND` ## Functions ### GifMaker ▸ **GifMaker**(): `Element` Entrypoint for gif and sprite generation, accessed via `yarn run gifs`. Wait a second or so for the textures to get loaded, then click the buttons to download files as a zip. gifs are saved as 60fps webm, and can take a while - open the console to see progress (logged verbosely) #### Returns `Element` ================================================ FILE: docs/modules/Frontend_Pages_LandingPage.md ================================================ # Module: Frontend/Pages/LandingPage ## Table of contents ### Enumerations - [LandingPageZIndex](../enums/Frontend_Pages_LandingPage.LandingPageZIndex.md) ### Variables - [LinkContainer](Frontend_Pages_LandingPage.md#linkcontainer) ### Functions - [default](Frontend_Pages_LandingPage.md#default) ## Variables ### LinkContainer • `Const` **LinkContainer**: `StyledComponent`<`"div"`, `any`, {}, `never`\> ## Functions ### default ▸ **default**(): `Element` #### Returns `Element` ================================================ FILE: docs/modules/Frontend_Pages_LoadingPage.md ================================================ # Module: Frontend/Pages/LoadingPage ## Table of contents ### Functions - [default](Frontend_Pages_LoadingPage.md#default) ## Functions ### default ▸ **default**(): `Element` #### Returns `Element` ================================================ FILE: docs/modules/Frontend_Pages_LobbyLandingPage.md ================================================ # Module: Frontend/Pages/LobbyLandingPage ## Table of contents ### Functions - [LobbyLandingPage](Frontend_Pages_LobbyLandingPage.md#lobbylandingpage) ## Functions ### LobbyLandingPage ▸ **LobbyLandingPage**(`__namedParameters`): `Element` #### Parameters | Name | Type | | :-------------------------- | :---------------------------------------- | | `__namedParameters` | `Object` | | `__namedParameters.onReady` | (`connection`: `EthConnection`) => `void` | #### Returns `Element` ================================================ FILE: docs/modules/Frontend_Pages_NotFoundPage.md ================================================ # Module: Frontend/Pages/NotFoundPage ## Table of contents ### Functions - [NotFoundPage](Frontend_Pages_NotFoundPage.md#notfoundpage) ## Functions ### NotFoundPage ▸ **NotFoundPage**(): `Element` #### Returns `Element` ================================================ FILE: docs/modules/Frontend_Pages_ShareArtifact.md ================================================ # Module: Frontend/Pages/ShareArtifact ## Table of contents ### Functions - [ShareArtifact](Frontend_Pages_ShareArtifact.md#shareartifact) ## Functions ### ShareArtifact ▸ **ShareArtifact**(`__namedParameters`): `Element` #### Parameters | Name | Type | | :------------------ | :--------------------------------------------------------------------------------- | | `__namedParameters` | `RouteComponentProps`<{ `artifactId`: `ArtifactId` }, `StaticContext`, `unknown`\> | #### Returns `Element` ================================================ FILE: docs/modules/Frontend_Pages_SharePlanet.md ================================================ # Module: Frontend/Pages/SharePlanet ## Table of contents ### Functions - [SharePlanet](Frontend_Pages_SharePlanet.md#shareplanet) ## Functions ### SharePlanet ▸ **SharePlanet**(`__namedParameters`): `Element` #### Parameters | Name | Type | | :------------------ | :--------------------------------------------------------------------------------- | | `__namedParameters` | `RouteComponentProps`<{ `locationId`: `LocationId` }, `StaticContext`, `unknown`\> | #### Returns `Element` ================================================ FILE: docs/modules/Frontend_Pages_TestArtifactImages.md ================================================ # Module: Frontend/Pages/TestArtifactImages ## Table of contents ### Functions - [TestArtifactImages](Frontend_Pages_TestArtifactImages.md#testartifactimages) ## Functions ### TestArtifactImages ▸ **TestArtifactImages**(): `Element` #### Returns `Element` ================================================ FILE: docs/modules/Frontend_Pages_TxConfirmPopup.md ================================================ # Module: Frontend/Pages/TxConfirmPopup ## Table of contents ### Functions - [TxConfirmPopup](Frontend_Pages_TxConfirmPopup.md#txconfirmpopup) ## Functions ### TxConfirmPopup ▸ **TxConfirmPopup**(`__namedParameters`): `Element` #### Parameters | Name | Type | | :------------------ | :---------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `__namedParameters` | `RouteComponentProps`<{ `actionId`: `string` ; `addr`: `string` ; `balance`: `string` ; `contract`: `string` ; `method`: `string` }, `StaticContext`, `unknown`\> | #### Returns `Element` ================================================ FILE: docs/modules/Frontend_Pages_UnsubscribePage.md ================================================ # Module: Frontend/Pages/UnsubscribePage ## Table of contents ### Enumerations - [LandingPageZIndex](../enums/Frontend_Pages_UnsubscribePage.LandingPageZIndex.md) ### Functions - [default](Frontend_Pages_UnsubscribePage.md#default) ## Functions ### default ▸ **default**(): `Element` #### Returns `Element` ================================================ FILE: docs/modules/Frontend_Pages_ValhallaPage.md ================================================ # Module: Frontend/Pages/ValhallaPage ## Table of contents ### Functions - [ValhallaPage](Frontend_Pages_ValhallaPage.md#valhallapage) ## Functions ### ValhallaPage ▸ **ValhallaPage**(): `Element` #### Returns `Element` ================================================ FILE: docs/modules/Frontend_Panes_ArtifactCard.md ================================================ # Module: Frontend/Panes/ArtifactCard ## Table of contents ### Functions - [ArtifactCard](Frontend_Panes_ArtifactCard.md#artifactcard) ## Functions ### ArtifactCard ▸ **ArtifactCard**(`__namedParameters`): `null` \| `Element` #### Parameters | Name | Type | | :------------------------------ | :----------- | | `__namedParameters` | `Object` | | `__namedParameters.artifactId?` | `ArtifactId` | #### Returns `null` \| `Element` ================================================ FILE: docs/modules/Frontend_Panes_ArtifactDetailsPane.md ================================================ # Module: Frontend/Panes/ArtifactDetailsPane ## Table of contents ### Functions - [ArtifactDetailsBody](Frontend_Panes_ArtifactDetailsPane.md#artifactdetailsbody) - [ArtifactDetailsHelpContent](Frontend_Panes_ArtifactDetailsPane.md#artifactdetailshelpcontent) - [ArtifactDetailsPane](Frontend_Panes_ArtifactDetailsPane.md#artifactdetailspane) - [UpgradeStatInfo](Frontend_Panes_ArtifactDetailsPane.md#upgradestatinfo) ## Functions ### ArtifactDetailsBody ▸ **ArtifactDetailsBody**(`__namedParameters`): `null` \| `Element` #### Parameters | Name | Type | | :------------------------------------ | :----------------------------------------------------------------------------------------------- | | `__namedParameters` | `Object` | | `__namedParameters.artifactId` | `ArtifactId` | | `__namedParameters.contractConstants` | [`ContractConstants`](../interfaces/types_darkforest_api_ContractsAPITypes.ContractConstants.md) | | `__namedParameters.depositOn?` | `LocationId` | | `__namedParameters.modal?` | [`ModalHandle`](../interfaces/Frontend_Views_ModalPane.ModalHandle.md) | | `__namedParameters.noActions?` | `boolean` | #### Returns `null` \| `Element` --- ### ArtifactDetailsHelpContent ▸ **ArtifactDetailsHelpContent**(): `Element` #### Returns `Element` --- ### ArtifactDetailsPane ▸ **ArtifactDetailsPane**(`__namedParameters`): `Element` #### Parameters | Name | Type | | :----------------------------- | :--------------------------------------------------------------------- | | `__namedParameters` | `Object` | | `__namedParameters.artifactId` | `ArtifactId` | | `__namedParameters.depositOn?` | `LocationId` | | `__namedParameters.modal` | [`ModalHandle`](../interfaces/Frontend_Views_ModalPane.ModalHandle.md) | #### Returns `Element` --- ### UpgradeStatInfo ▸ **UpgradeStatInfo**(`__namedParameters`): `Element` #### Parameters | Name | Type | | :--------------------------- | :-------------------------------------------------------- | | `__namedParameters` | `Object` | | `__namedParameters.stat` | [`StatIdx`](../enums/types_global_GlobalTypes.StatIdx.md) | | `__namedParameters.upgrades` | (`undefined` \| `Upgrade`)[] | #### Returns `Element` ================================================ FILE: docs/modules/Frontend_Panes_ArtifactHoverPane.md ================================================ # Module: Frontend/Panes/ArtifactHoverPane ## Table of contents ### Functions - [ArtifactHoverPane](Frontend_Panes_ArtifactHoverPane.md#artifacthoverpane) ## Functions ### ArtifactHoverPane ▸ **ArtifactHoverPane**(): `Element` #### Returns `Element` ================================================ FILE: docs/modules/Frontend_Panes_ArtifactsList.md ================================================ # Module: Frontend/Panes/ArtifactsList ## Table of contents ### Functions - [AllArtifacts](Frontend_Panes_ArtifactsList.md#allartifacts) - [ArtifactsList](Frontend_Panes_ArtifactsList.md#artifactslist) - [ShipList](Frontend_Panes_ArtifactsList.md#shiplist) ## Functions ### AllArtifacts ▸ **AllArtifacts**(`__namedParameters`): `Element` #### Parameters | Name | Type | | :-------------------------------------- | :--------------------------------------------------------------------- | | `__namedParameters` | `Object` | | `__namedParameters.artifacts` | `Artifact`[] | | `__namedParameters.depositOn?` | `LocationId` | | `__namedParameters.maxRarity?` | `number` | | `__namedParameters.modal` | [`ModalHandle`](../interfaces/Frontend_Views_ModalPane.ModalHandle.md) | | `__namedParameters.noArtifactsMessage?` | `ReactElement`<`any`, `string` \| `JSXElementConstructor`<`any`\>\> | | `__namedParameters.noShipsMessage?` | `ReactElement`<`any`, `string` \| `JSXElementConstructor`<`any`\>\> | #### Returns `Element` --- ### ArtifactsList ▸ **ArtifactsList**(`__namedParameters`): `Element` #### Parameters | Name | Type | | :-------------------------------------- | :--------------------------------------------------------------------- | | `__namedParameters` | `Object` | | `__namedParameters.artifacts` | `Artifact`[] | | `__namedParameters.depositOn?` | `LocationId` | | `__namedParameters.maxRarity?` | `number` | | `__namedParameters.modal` | [`ModalHandle`](../interfaces/Frontend_Views_ModalPane.ModalHandle.md) | | `__namedParameters.noArtifactsMessage?` | `ReactElement`<`any`, `string` \| `JSXElementConstructor`<`any`\>\> | #### Returns `Element` --- ### ShipList ▸ **ShipList**(`__namedParameters`): `Element` #### Parameters | Name | Type | | :---------------------------------- | :--------------------------------------------------------------------- | | `__namedParameters` | `Object` | | `__namedParameters.artifacts` | `Artifact`[] | | `__namedParameters.depositOn?` | `LocationId` | | `__namedParameters.modal` | [`ModalHandle`](../interfaces/Frontend_Views_ModalPane.ModalHandle.md) | | `__namedParameters.noShipsMessage?` | `ReactElement`<`any`, `string` \| `JSXElementConstructor`<`any`\>\> | #### Returns `Element` ================================================ FILE: docs/modules/Frontend_Panes_BroadcastPane.md ================================================ # Module: Frontend/Panes/BroadcastPane ## Table of contents ### Functions - [BroadcastPane](Frontend_Panes_BroadcastPane.md#broadcastpane) - [BroadcastPaneHelpContent](Frontend_Panes_BroadcastPane.md#broadcastpanehelpcontent) ## Functions ### BroadcastPane ▸ **BroadcastPane**(`__namedParameters`): `Element` #### Parameters | Name | Type | | :---------------------------------- | :--------------------------------------------------------------------- | | `__namedParameters` | `Object` | | `__namedParameters.initialPlanetId` | `undefined` \| `LocationId` | | `__namedParameters.modal` | [`ModalHandle`](../interfaces/Frontend_Views_ModalPane.ModalHandle.md) | #### Returns `Element` --- ### BroadcastPaneHelpContent ▸ **BroadcastPaneHelpContent**(): `Element` #### Returns `Element` ================================================ FILE: docs/modules/Frontend_Panes_CoordsPane.md ================================================ # Module: Frontend/Panes/CoordsPane ## Table of contents ### Functions - [CoordsPane](Frontend_Panes_CoordsPane.md#coordspane) ## Functions ### CoordsPane ▸ **CoordsPane**(): `Element` #### Returns `Element` ================================================ FILE: docs/modules/Frontend_Panes_DiagnosticsPane.md ================================================ # Module: Frontend/Panes/DiagnosticsPane ## Table of contents ### Functions - [DiagnosticsPane](Frontend_Panes_DiagnosticsPane.md#diagnosticspane) ## Functions ### DiagnosticsPane ▸ **DiagnosticsPane**(`__namedParameters`): `Element` #### Parameters | Name | Type | | :-------------------------- | :----------- | | `__namedParameters` | `Object` | | `__namedParameters.visible` | `boolean` | | `__namedParameters.onClose` | () => `void` | #### Returns `Element` ================================================ FILE: docs/modules/Frontend_Panes_ExplorePane.md ================================================ # Module: Frontend/Panes/ExplorePane ## Table of contents ### Functions - [ExplorePane](Frontend_Panes_ExplorePane.md#explorepane) ## Functions ### ExplorePane ▸ **ExplorePane**(): `Element` #### Returns `Element` ================================================ FILE: docs/modules/Frontend_Panes_HatPane.md ================================================ # Module: Frontend/Panes/HatPane ## Table of contents ### Functions - [HatPane](Frontend_Panes_HatPane.md#hatpane) ## Functions ### HatPane ▸ **HatPane**(`__namedParameters`): `Element` #### Parameters | Name | Type | | :----------------------------------- | :--------------------------------------------------------------------- | | `__namedParameters` | `Object` | | `__namedParameters.initialPlanetId?` | `LocationId` | | `__namedParameters.modal` | [`ModalHandle`](../interfaces/Frontend_Views_ModalPane.ModalHandle.md) | #### Returns `Element` ================================================ FILE: docs/modules/Frontend_Panes_HelpPane.md ================================================ # Module: Frontend/Panes/HelpPane ## Table of contents ### Functions - [HelpPane](Frontend_Panes_HelpPane.md#helppane) ## Functions ### HelpPane ▸ **HelpPane**(`__namedParameters`): `Element` #### Parameters | Name | Type | | :-------------------------- | :----------- | | `__namedParameters` | `Object` | | `__namedParameters.visible` | `boolean` | | `__namedParameters.onClose` | () => `void` | #### Returns `Element` ================================================ FILE: docs/modules/Frontend_Panes_HoverPane.md ================================================ # Module: Frontend/Panes/HoverPane ## Table of contents ### Functions - [HoverPane](Frontend_Panes_HoverPane.md#hoverpane) ## Functions ### HoverPane ▸ **HoverPane**(`__namedParameters`): `Element` This is the pane that is rendered when you hover over a planet. #### Parameters | Name | Type | | :-------------------------- | :-------------- | | `__namedParameters` | `Object` | | `__namedParameters.element` | `ReactChild` | | `__namedParameters.style?` | `CSSProperties` | | `__namedParameters.visible` | `boolean` | #### Returns `Element` ================================================ FILE: docs/modules/Frontend_Panes_HoverPlanetPane.md ================================================ # Module: Frontend/Panes/HoverPlanetPane ## Table of contents ### Functions - [HoverPlanetPane](Frontend_Panes_HoverPlanetPane.md#hoverplanetpane) ## Functions ### HoverPlanetPane ▸ **HoverPlanetPane**(): `Element` This is the pane that is rendered when you hover over a planet. #### Returns `Element` ================================================ FILE: docs/modules/Frontend_Panes_Lobbies_AdminPermissionsPane.md ================================================ # Module: Frontend/Panes/Lobbies/AdminPermissionsPane ## Table of contents ### Functions - [AdminPermissionsPane](Frontend_Panes_Lobbies_AdminPermissionsPane.md#adminpermissionspane) ## Functions ### AdminPermissionsPane ▸ **AdminPermissionsPane**(`__namedParameters`): `Element` #### Parameters | Name | Type | | :------------------ | :------------------------------------------------------------------------------------------ | | `__namedParameters` | [`LobbiesPaneProps`](../interfaces/Frontend_Panes_Lobbies_LobbiesUtils.LobbiesPaneProps.md) | #### Returns `Element` ================================================ FILE: docs/modules/Frontend_Panes_Lobbies_ArtifactSettingsPane.md ================================================ # Module: Frontend/Panes/Lobbies/ArtifactSettingsPane ## Table of contents ### Functions - [ArtifactSettingsPane](Frontend_Panes_Lobbies_ArtifactSettingsPane.md#artifactsettingspane) ## Functions ### ArtifactSettingsPane ▸ **ArtifactSettingsPane**(`__namedParameters`): `Element` #### Parameters | Name | Type | | :------------------ | :------------------------------------------------------------------------------------------ | | `__namedParameters` | [`LobbiesPaneProps`](../interfaces/Frontend_Panes_Lobbies_LobbiesUtils.LobbiesPaneProps.md) | #### Returns `Element` ================================================ FILE: docs/modules/Frontend_Panes_Lobbies_CaptureZonesPane.md ================================================ # Module: Frontend/Panes/Lobbies/CaptureZonesPane ## Table of contents ### Functions - [CaptureZonesPane](Frontend_Panes_Lobbies_CaptureZonesPane.md#capturezonespane) ## Functions ### CaptureZonesPane ▸ **CaptureZonesPane**(`__namedParameters`): `Element` #### Parameters | Name | Type | | :------------------ | :------------------------------------------------------------------------------------------ | | `__namedParameters` | [`LobbiesPaneProps`](../interfaces/Frontend_Panes_Lobbies_LobbiesUtils.LobbiesPaneProps.md) | #### Returns `Element` ================================================ FILE: docs/modules/Frontend_Panes_Lobbies_ConfigurationPane.md ================================================ # Module: Frontend/Panes/Lobbies/ConfigurationPane ## Table of contents ### Functions - [ConfigurationPane](Frontend_Panes_Lobbies_ConfigurationPane.md#configurationpane) ## Functions ### ConfigurationPane ▸ **ConfigurationPane**(`__namedParameters`): `Element` #### Parameters | Name | Type | | :--------------------------------- | :----------------------------------------------------------------------------------------------------------- | | `__namedParameters` | `Object` | | `__namedParameters.lobbyAddress` | `undefined` \| `EthAddress` | | `__namedParameters.modalIndex` | `number` | | `__namedParameters.startingConfig` | [`LobbyInitializers`](Frontend_Panes_Lobbies_Reducer.md#lobbyinitializers) | | `__namedParameters.onCreate` | (`config`: [`LobbyInitializers`](Frontend_Panes_Lobbies_Reducer.md#lobbyinitializers)) => `Promise`<`void`\> | | `__namedParameters.onMapChange` | (`props`: [`MinimapConfig`](Frontend_Panes_Lobbies_MinimapUtils.md#minimapconfig)) => `void` | #### Returns `Element` ================================================ FILE: docs/modules/Frontend_Panes_Lobbies_GameSettingsPane.md ================================================ # Module: Frontend/Panes/Lobbies/GameSettingsPane ## Table of contents ### Functions - [GameSettingsPane](Frontend_Panes_Lobbies_GameSettingsPane.md#gamesettingspane) ## Functions ### GameSettingsPane ▸ **GameSettingsPane**(`__namedParameters`): `Element` #### Parameters | Name | Type | | :------------------ | :------------------------------------------------------------------------------------------ | | `__namedParameters` | [`LobbiesPaneProps`](../interfaces/Frontend_Panes_Lobbies_LobbiesUtils.LobbiesPaneProps.md) | #### Returns `Element` ================================================ FILE: docs/modules/Frontend_Panes_Lobbies_LobbiesUtils.md ================================================ # Module: Frontend/Panes/Lobbies/LobbiesUtils ## Table of contents ### Interfaces - [LobbiesPaneProps](../interfaces/Frontend_Panes_Lobbies_LobbiesUtils.LobbiesPaneProps.md) ### Variables - [ButtonRow](Frontend_Panes_Lobbies_LobbiesUtils.md#buttonrow) ### Functions - [ConfigDownload](Frontend_Panes_Lobbies_LobbiesUtils.md#configdownload) - [ConfigUpload](Frontend_Panes_Lobbies_LobbiesUtils.md#configupload) - [LinkButton](Frontend_Panes_Lobbies_LobbiesUtils.md#linkbutton) - [NavigationTitle](Frontend_Panes_Lobbies_LobbiesUtils.md#navigationtitle) - [Warning](Frontend_Panes_Lobbies_LobbiesUtils.md#warning) ## Variables ### ButtonRow • `Const` **ButtonRow**: `StyledComponent`<`ForwardRefExoticComponent`<`Partial`<`Omit`<`DarkForestRow`, `"children"`\>\> & `Events`<`unknown`\> & `HTMLAttributes`<`HTMLElement`\> & {} & `RefAttributes`<`unknown`\>\>, `any`, {}, `never`\> ## Functions ### ConfigDownload ▸ **ConfigDownload**(`__namedParameters`): `Element` #### Parameters | Name | Type | | :-------------------------- | :----------------------------------------------------------------------- | | `__namedParameters` | `Object` | | `__namedParameters.address` | `undefined` \| `EthAddress` | | `__namedParameters.config` | [`LobbyConfigState`](Frontend_Panes_Lobbies_Reducer.md#lobbyconfigstate) | | `__namedParameters.onError` | (`msg`: `string`) => `void` | #### Returns `Element` --- ### ConfigUpload ▸ **ConfigUpload**(`__namedParameters`): `Element` #### Parameters | Name | Type | | :--------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `__namedParameters` | `Object` | | `__namedParameters.onError` | (`msg`: `string`) => `void` | | `__namedParameters.onUpload` | (`initializers`: { `ABANDON_RANGE_CHANGE_PERCENT`: `number` ; `ABANDON_SPEED_CHANGE_PERCENT`: `number` ; `ADMIN_CAN_ADD_PLANETS`: `boolean` ; `ARTIFACT_POINT_VALUES`: `Tuple6`<`number`\> ; `BIOMEBASE_KEY`: `number` ; `BIOME_THRESHOLD_1`: `number` ; `BIOME_THRESHOLD_2`: `number` ; `CAPTURE_ZONES_ENABLED`: `boolean` ; `CAPTURE_ZONES_PER_5000_WORLD_RADIUS`: `number` ; `CAPTURE_ZONE_CHANGE_BLOCK_INTERVAL`: `number` ; `CAPTURE_ZONE_COUNT`: `number` ; `CAPTURE_ZONE_HOLD_BLOCKS_REQUIRED`: `number` ; `CAPTURE_ZONE_PLANET_LEVEL_SCORE`: `ExactArray10`<`number`\> ; `CAPTURE_ZONE_RADIUS`: `number` ; `CLAIM_PLANET_COOLDOWN`: `number` ; `DISABLE_ZK_CHECKS`: `boolean` ; `INIT_PERLIN_MAX`: `number` ; `INIT_PERLIN_MIN`: `number` ; `LOCATION_REVEAL_COOLDOWN`: `number` ; `MAX_NATURAL_PLANET_LEVEL`: `number` ; `PERLIN_LENGTH_SCALE`: `number` ; `PERLIN_MIRROR_X`: `boolean` ; `PERLIN_MIRROR_Y`: `boolean` ; `PERLIN_THRESHOLD_1`: `number` ; `PERLIN_THRESHOLD_2`: `number` ; `PERLIN_THRESHOLD_3`: `number` ; `PHOTOID_ACTIVATION_DELAY`: `number` ; `PLANETHASH_KEY`: `number` ; `PLANET_LEVEL_JUNK`: `ExactArray10`<`number`\> ; `PLANET_LEVEL_THRESHOLDS`: `ExactArray10`<`number`\> ; `PLANET_RARITY`: `number` ; `PLANET_TRANSFER_ENABLED`: `boolean` ; `PLANET_TYPE_WEIGHTS`: `PlanetTypeWeights` ; `SILVER_SCORE_VALUE`: `number` ; `SPACETYPE_KEY`: `number` ; `SPACE_JUNK_ENABLED`: `boolean` ; `SPACE_JUNK_LIMIT`: `number` ; `SPAWN_RIM_AREA`: `number` ; `START_PAUSED`: `boolean` ; `TIME_FACTOR_HUNDREDTHS`: `number` ; `TOKEN_MINT_END_TIMESTAMP`: `number` ; `WORLD_RADIUS_LOCKED`: `boolean` ; `WORLD_RADIUS_MIN`: `number` }) => `void` | #### Returns `Element` --- ### LinkButton ▸ **LinkButton**(`__namedParameters`): `Element` #### Parameters | Name | Type | | :------------------ | :--------------------------------------------------------------- | | `__namedParameters` | `PropsWithChildren`<{ `shortcut?`: `string` ; `to`: `string` }\> | #### Returns `Element` --- ### NavigationTitle ▸ **NavigationTitle**(`__namedParameters`): `Element` #### Parameters | Name | Type | | :------------------ | :------- | | `__namedParameters` | `Object` | #### Returns `Element` --- ### Warning ▸ **Warning**(`__namedParameters`): `null` \| `Element` #### Parameters | Name | Type | | :------------------ | :------- | | `__namedParameters` | `Object` | #### Returns `null` \| `Element` ================================================ FILE: docs/modules/Frontend_Panes_Lobbies_MinimapPane.md ================================================ # Module: Frontend/Panes/Lobbies/MinimapPane ## Table of contents ### Functions - [Minimap](Frontend_Panes_Lobbies_MinimapPane.md#minimap) ## Functions ### Minimap ▸ **Minimap**(`__namedParameters`): `Element` #### Parameters | Name | Type | | :----------------------------- | :------------------------------------------------------------------------------------- | | `__namedParameters` | `Object` | | `__namedParameters.config` | `undefined` \| [`MinimapConfig`](Frontend_Panes_Lobbies_MinimapUtils.md#minimapconfig) | | `__namedParameters.modalIndex` | `number` | #### Returns `Element` ================================================ FILE: docs/modules/Frontend_Panes_Lobbies_MinimapUtils.md ================================================ # Module: Frontend/Panes/Lobbies/MinimapUtils ## Table of contents ### Type aliases - [DrawMessage](Frontend_Panes_Lobbies_MinimapUtils.md#drawmessage) - [MinimapConfig](Frontend_Panes_Lobbies_MinimapUtils.md#minimapconfig) ## Type aliases ### DrawMessage Ƭ **DrawMessage**: `Object` #### Type declaration | Name | Type | | :------- | :-------------------------------------------------------- | | `data` | { `type`: `SpaceType` ; `x`: `number` ; `y`: `number` }[] | | `radius` | `number` | --- ### MinimapConfig Ƭ **MinimapConfig**: `Object` #### Type declaration | Name | Type | | :----------------- | :-------- | | `key` | `number` | | `mirrorX` | `boolean` | | `mirrorY` | `boolean` | | `perlinThreshold1` | `number` | | `perlinThreshold2` | `number` | | `perlinThreshold3` | `number` | | `scale` | `number` | | `worldRadius` | `number` | ================================================ FILE: docs/modules/Frontend_Panes_Lobbies_PlanetPane.md ================================================ # Module: Frontend/Panes/Lobbies/PlanetPane ## Table of contents ### Functions - [PlanetPane](Frontend_Panes_Lobbies_PlanetPane.md#planetpane) ## Functions ### PlanetPane ▸ **PlanetPane**(`__namedParameters`): `Element` #### Parameters | Name | Type | | :------------------ | :------------------------------------------------------------------------------------------ | | `__namedParameters` | [`LobbiesPaneProps`](../interfaces/Frontend_Panes_Lobbies_LobbiesUtils.LobbiesPaneProps.md) | #### Returns `Element` ================================================ FILE: docs/modules/Frontend_Panes_Lobbies_PlayerSpawnPane.md ================================================ # Module: Frontend/Panes/Lobbies/PlayerSpawnPane ## Table of contents ### Functions - [PlayerSpawnPane](Frontend_Panes_Lobbies_PlayerSpawnPane.md#playerspawnpane) ## Functions ### PlayerSpawnPane ▸ **PlayerSpawnPane**(`__namedParameters`): `Element` #### Parameters | Name | Type | | :------------------ | :------------------------------------------------------------------------------------------ | | `__namedParameters` | [`LobbiesPaneProps`](../interfaces/Frontend_Panes_Lobbies_LobbiesUtils.LobbiesPaneProps.md) | #### Returns `Element` ================================================ FILE: docs/modules/Frontend_Panes_Lobbies_Reducer.md ================================================ # Module: Frontend/Panes/Lobbies/Reducer ## Table of contents ### Classes - [InvalidConfigError](../classes/Frontend_Panes_Lobbies_Reducer.InvalidConfigError.md) ### Type aliases - [LobbyAction](Frontend_Panes_Lobbies_Reducer.md#lobbyaction) - [LobbyConfigAction](Frontend_Panes_Lobbies_Reducer.md#lobbyconfigaction) - [LobbyConfigState](Frontend_Panes_Lobbies_Reducer.md#lobbyconfigstate) - [LobbyInitializers](Frontend_Panes_Lobbies_Reducer.md#lobbyinitializers) ### Variables - [SAFE_UPPER_BOUNDS](Frontend_Panes_Lobbies_Reducer.md#safe_upper_bounds) ### Functions - [lobbyConfigInit](Frontend_Panes_Lobbies_Reducer.md#lobbyconfiginit) - [lobbyConfigReducer](Frontend_Panes_Lobbies_Reducer.md#lobbyconfigreducer) - [ofAny](Frontend_Panes_Lobbies_Reducer.md#ofany) - [ofArtifactPointValues](Frontend_Panes_Lobbies_Reducer.md#ofartifactpointvalues) - [ofBoolean](Frontend_Panes_Lobbies_Reducer.md#ofboolean) - [ofCaptureZoneChangeBlockInterval](Frontend_Panes_Lobbies_Reducer.md#ofcapturezonechangeblockinterval) - [ofCaptureZonePlanetLevelScore](Frontend_Panes_Lobbies_Reducer.md#ofcapturezoneplanetlevelscore) - [ofCaptureZoneRadius](Frontend_Panes_Lobbies_Reducer.md#ofcapturezoneradius) - [ofCaptureZonesPer5000WorldRadius](Frontend_Panes_Lobbies_Reducer.md#ofcapturezonesper5000worldradius) - [ofMaxNaturalPlanetLevel](Frontend_Panes_Lobbies_Reducer.md#ofmaxnaturalplanetlevel) - [ofNoop](Frontend_Panes_Lobbies_Reducer.md#ofnoop) - [ofPerlinLengthScale](Frontend_Panes_Lobbies_Reducer.md#ofperlinlengthscale) - [ofPlanetLevelJunk](Frontend_Panes_Lobbies_Reducer.md#ofplanetleveljunk) - [ofPlanetLevelThresholds](Frontend_Panes_Lobbies_Reducer.md#ofplanetlevelthresholds) - [ofPlanetRarity](Frontend_Panes_Lobbies_Reducer.md#ofplanetrarity) - [ofPositiveFloat](Frontend_Panes_Lobbies_Reducer.md#ofpositivefloat) - [ofPositiveInteger](Frontend_Panes_Lobbies_Reducer.md#ofpositiveinteger) - [ofPositivePercent](Frontend_Panes_Lobbies_Reducer.md#ofpositivepercent) - [ofSpaceJunkLimit](Frontend_Panes_Lobbies_Reducer.md#ofspacejunklimit) - [ofSpawnRimArea](Frontend_Panes_Lobbies_Reducer.md#ofspawnrimarea) - [ofTimeFactorHundredths](Frontend_Panes_Lobbies_Reducer.md#oftimefactorhundredths) - [ofWorldRadiusMin](Frontend_Panes_Lobbies_Reducer.md#ofworldradiusmin) - [toInitializers](Frontend_Panes_Lobbies_Reducer.md#toinitializers) ## Type aliases ### LobbyAction Ƭ **LobbyAction**: { `type`: `"RESET"` ; `value`: [`LobbyConfigState`](Frontend_Panes_Lobbies_Reducer.md#lobbyconfigstate) } \| [`LobbyConfigAction`](Frontend_Panes_Lobbies_Reducer.md#lobbyconfigaction) --- ### LobbyConfigAction Ƭ **LobbyConfigAction**: { `type`: `"START_PAUSED"` ; `value`: `Initializers`[``"START_PAUSED"``] \| `undefined` } \| { `type`: `"ADMIN_CAN_ADD_PLANETS"` ; `value`: `Initializers`[``"ADMIN_CAN_ADD_PLANETS"``] \| `undefined` } \| { `type`: `"TOKEN_MINT_END_TIMESTAMP"` ; `value`: `Initializers`[``"TOKEN_MINT_END_TIMESTAMP"``] \| `undefined` } \| { `type`: `"WORLD_RADIUS_LOCKED"` ; `value`: `Initializers`[``"WORLD_RADIUS_LOCKED"``] \| `undefined` } \| { `type`: `"WORLD_RADIUS_MIN"` ; `value`: `Initializers`[``"WORLD_RADIUS_MIN"``] \| `undefined` } \| { `type`: `"DISABLE_ZK_CHECKS"` ; `value`: `Initializers`[``"DISABLE_ZK_CHECKS"``] \| `undefined` } \| { `type`: `"PLANETHASH_KEY"` ; `value`: `Initializers`[``"PLANETHASH_KEY"``] \| `undefined` } \| { `type`: `"SPACETYPE_KEY"` ; `value`: `Initializers`[``"SPACETYPE_KEY"``] \| `undefined` } \| { `type`: `"BIOMEBASE_KEY"` ; `value`: `Initializers`[``"BIOMEBASE_KEY"``] \| `undefined` } \| { `type`: `"PERLIN_MIRROR_X"` ; `value`: `Initializers`[``"PERLIN_MIRROR_X"``] \| `undefined` } \| { `type`: `"PERLIN_MIRROR_Y"` ; `value`: `Initializers`[``"PERLIN_MIRROR_Y"``] \| `undefined` } \| { `type`: `"PERLIN_LENGTH_SCALE"` ; `value`: `Initializers`[``"PERLIN_LENGTH_SCALE"``] \| `undefined` } \| { `type`: `"MAX_NATURAL_PLANET_LEVEL"` ; `value`: `Initializers`[``"MAX_NATURAL_PLANET_LEVEL"``] \| `undefined` } \| { `type`: `"TIME_FACTOR_HUNDREDTHS"` ; `value`: `Initializers`[``"TIME_FACTOR_HUNDREDTHS"``] \| `undefined` } \| { `type`: `"PERLIN_THRESHOLD_1"` ; `value`: `Initializers`[``"PERLIN_THRESHOLD_1"``] \| `undefined` } \| { `type`: `"PERLIN_THRESHOLD_2"` ; `value`: `Initializers`[``"PERLIN_THRESHOLD_2"``] \| `undefined` } \| { `type`: `"PERLIN_THRESHOLD_3"` ; `value`: `Initializers`[``"PERLIN_THRESHOLD_3"``] \| `undefined` } \| { `type`: `"INIT_PERLIN_MIN"` ; `value`: `Initializers`[``"INIT_PERLIN_MIN"``] \| `undefined` } \| { `type`: `"INIT_PERLIN_MAX"` ; `value`: `Initializers`[``"INIT_PERLIN_MAX"``] \| `undefined` } \| { `type`: `"BIOME_THRESHOLD_1"` ; `value`: `Initializers`[``"BIOME_THRESHOLD_1"``] \| `undefined` } \| { `type`: `"BIOME_THRESHOLD_2"` ; `value`: `Initializers`[``"BIOME_THRESHOLD_2"``] \| `undefined` } \| { `index`: `number` ; `type`: `"PLANET_LEVEL_THRESHOLDS"` ; `value`: `number` \| `undefined` } \| { `type`: `"PLANET_RARITY"` ; `value`: `Initializers`[``"PLANET_RARITY"``] \| `undefined` } \| { `type`: `"PLANET_TRANSFER_ENABLED"` ; `value`: `Initializers`[``"PLANET_TRANSFER_ENABLED"``] \| `undefined` } \| { `type`: `"PHOTOID_ACTIVATION_DELAY"` ; `value`: `Initializers`[``"PHOTOID_ACTIVATION_DELAY"``] \| `undefined` } \| { `type`: `"SPAWN_RIM_AREA"` ; `value`: `Initializers`[``"SPAWN_RIM_AREA"``] \| `undefined` } \| { `type`: `"LOCATION_REVEAL_COOLDOWN"` ; `value`: `Initializers`[``"LOCATION_REVEAL_COOLDOWN"``] \| `undefined` } \| { `type`: `"PLANET_TYPE_WEIGHTS"` ; `value`: `Initializers`[``"PLANET_TYPE_WEIGHTS"``] \| `undefined` } \| { `type`: `"SILVER_SCORE_VALUE"` ; `value`: `Initializers`[``"SILVER_SCORE_VALUE"``] \| `undefined` } \| { `index`: `number` ; `type`: `"ARTIFACT_POINT_VALUES"` ; `value`: `number` \| `undefined` } \| { `type`: `"SPACE_JUNK_ENABLED"` ; `value`: `Initializers`[``"SPACE_JUNK_ENABLED"``] \| `undefined` } \| { `type`: `"SPACE_JUNK_LIMIT"` ; `value`: `Initializers`[``"SPACE_JUNK_LIMIT"``] \| `undefined` } \| { `index`: `number` ; `type`: `"PLANET_LEVEL_JUNK"` ; `value`: `number` \| `undefined` } \| { `type`: `"ABANDON_SPEED_CHANGE_PERCENT"` ; `value`: `Initializers`[``"ABANDON_SPEED_CHANGE_PERCENT"``] \| `undefined` } \| { `type`: `"ABANDON_RANGE_CHANGE_PERCENT"` ; `value`: `Initializers`[``"ABANDON_RANGE_CHANGE_PERCENT"``] \| `undefined` } \| { `type`: `"CAPTURE_ZONES_ENABLED"` ; `value`: `Initializers`[``"CAPTURE_ZONES_ENABLED"``] \| `undefined` } \| { `type`: `"CAPTURE_ZONE_CHANGE_BLOCK_INTERVAL"` ; `value`: `Initializers`[``"CAPTURE_ZONE_CHANGE_BLOCK_INTERVAL"``] \| `undefined` } \| { `type`: `"CAPTURE_ZONE_RADIUS"` ; `value`: `Initializers`[``"CAPTURE_ZONE_RADIUS"``] \| `undefined` } \| { `index`: `number` ; `type`: `"CAPTURE_ZONE_PLANET_LEVEL_SCORE"` ; `value`: `number` \| `undefined` } \| { `type`: `"CAPTURE_ZONE_HOLD_BLOCKS_REQUIRED"` ; `value`: `Initializers`[``"CAPTURE_ZONE_HOLD_BLOCKS_REQUIRED"``] \| `undefined` } \| { `type`: `"CAPTURE_ZONES_PER_5000_WORLD_RADIUS"` ; `value`: `Initializers`[``"CAPTURE_ZONES_PER_5000_WORLD_RADIUS"``] \| `undefined` } \| { `type`: `"WHITELIST_ENABLED"` ; `value`: `boolean` \| `undefined` } --- ### LobbyConfigState Ƭ **LobbyConfigState**: { [key in keyof LobbyInitializers]: Object } --- ### LobbyInitializers Ƭ **LobbyInitializers**: `Initializers` & { `WHITELIST_ENABLED`: `boolean` \| `undefined` } ## Variables ### SAFE_UPPER_BOUNDS • `Const` **SAFE_UPPER_BOUNDS**: `number` ## Functions ### lobbyConfigInit ▸ **lobbyConfigInit**(`startingConfig`): [`LobbyConfigState`](Frontend_Panes_Lobbies_Reducer.md#lobbyconfigstate) #### Parameters | Name | Type | | :--------------- | :------------------------------------------------------------------------- | | `startingConfig` | [`LobbyInitializers`](Frontend_Panes_Lobbies_Reducer.md#lobbyinitializers) | #### Returns [`LobbyConfigState`](Frontend_Panes_Lobbies_Reducer.md#lobbyconfigstate) --- ### lobbyConfigReducer ▸ **lobbyConfigReducer**(`state`, `action`): [`LobbyConfigState`](Frontend_Panes_Lobbies_Reducer.md#lobbyconfigstate) #### Parameters | Name | Type | | :------- | :----------------------------------------------------------------------- | | `state` | [`LobbyConfigState`](Frontend_Panes_Lobbies_Reducer.md#lobbyconfigstate) | | `action` | [`LobbyAction`](Frontend_Panes_Lobbies_Reducer.md#lobbyaction) | #### Returns [`LobbyConfigState`](Frontend_Panes_Lobbies_Reducer.md#lobbyconfigstate) --- ### ofAny ▸ **ofAny**(`__namedParameters`, `state`): { `currentValue`: `undefined` \| `number` \| `boolean` \| `PlanetTypeWeights` = value; `defaultValue`: `ExactArray10`<`number`\> ; `displayValue`: `undefined` \| `number` \| `boolean` \| `PlanetTypeWeights` = value; `warning`: `undefined` = undefined } \| { `currentValue`: `undefined` \| `number` \| `boolean` \| `PlanetTypeWeights` = value; `defaultValue`: `ExactArray10`<`number`\> ; `displayValue`: `undefined` \| `number` \| `boolean` \| `PlanetTypeWeights` = value; `warning`: `undefined` = undefined } \| { `currentValue`: `undefined` \| `number` \| `boolean` \| `PlanetTypeWeights` = value; `defaultValue`: `boolean` ; `displayValue`: `undefined` \| `number` \| `boolean` \| `PlanetTypeWeights` = value; `warning`: `undefined` = undefined } \| { `currentValue`: `undefined` \| `number` \| `boolean` \| `PlanetTypeWeights` = value; `defaultValue`: `boolean` ; `displayValue`: `undefined` \| `number` \| `boolean` \| `PlanetTypeWeights` = value; `warning`: `undefined` = undefined } \| { `currentValue`: `undefined` \| `number` \| `boolean` \| `PlanetTypeWeights` = value; `defaultValue`: `number` ; `displayValue`: `undefined` \| `number` \| `boolean` \| `PlanetTypeWeights` = value; `warning`: `undefined` = undefined } \| { `currentValue`: `undefined` \| `number` \| `boolean` \| `PlanetTypeWeights` = value; `defaultValue`: `boolean` ; `displayValue`: `undefined` \| `number` \| `boolean` \| `PlanetTypeWeights` = value; `warning`: `undefined` = undefined } \| { `currentValue`: `undefined` \| `number` \| `boolean` \| `PlanetTypeWeights` = value; `defaultValue`: `number` ; `displayValue`: `undefined` \| `number` \| `boolean` \| `PlanetTypeWeights` = value; `warning`: `undefined` = undefined } \| { `currentValue`: `undefined` \| `number` \| `boolean` \| `PlanetTypeWeights` = value; `defaultValue`: `boolean` ; `displayValue`: `undefined` \| `number` \| `boolean` \| `PlanetTypeWeights` = value; `warning`: `undefined` = undefined } \| { `currentValue`: `undefined` \| `number` \| `boolean` \| `PlanetTypeWeights` = value; `defaultValue`: `number` ; `displayValue`: `undefined` \| `number` \| `boolean` \| `PlanetTypeWeights` = value; `warning`: `undefined` = undefined } \| { `currentValue`: `undefined` \| `number` \| `boolean` \| `PlanetTypeWeights` = value; `defaultValue`: `number` ; `displayValue`: `undefined` \| `number` \| `boolean` \| `PlanetTypeWeights` = value; `warning`: `undefined` = undefined } \| { `currentValue`: `undefined` \| `number` \| `boolean` \| `PlanetTypeWeights` = value; `defaultValue`: `number` ; `displayValue`: `undefined` \| `number` \| `boolean` \| `PlanetTypeWeights` = value; `warning`: `undefined` = undefined } \| { `currentValue`: `undefined` \| `number` \| `boolean` \| `PlanetTypeWeights` = value; `defaultValue`: `boolean` ; `displayValue`: `undefined` \| `number` \| `boolean` \| `PlanetTypeWeights` = value; `warning`: `undefined` = undefined } \| { `currentValue`: `undefined` \| `number` \| `boolean` \| `PlanetTypeWeights` = value; `defaultValue`: `boolean` ; `displayValue`: `undefined` \| `number` \| `boolean` \| `PlanetTypeWeights` = value; `warning`: `undefined` = undefined } \| { `currentValue`: `undefined` \| `number` \| `boolean` \| `PlanetTypeWeights` = value; `defaultValue`: `number` ; `displayValue`: `undefined` \| `number` \| `boolean` \| `PlanetTypeWeights` = value; `warning`: `undefined` = undefined } \| { `currentValue`: `undefined` \| `number` \| `boolean` \| `PlanetTypeWeights` = value; `defaultValue`: `number` ; `displayValue`: `undefined` \| `number` \| `boolean` \| `PlanetTypeWeights` = value; `warning`: `undefined` = undefined } \| { `currentValue`: `undefined` \| `number` \| `boolean` \| `PlanetTypeWeights` = value; `defaultValue`: `number` ; `displayValue`: `undefined` \| `number` \| `boolean` \| `PlanetTypeWeights` = value; `warning`: `undefined` = undefined } \| { `currentValue`: `undefined` \| `number` \| `boolean` \| `PlanetTypeWeights` = value; `defaultValue`: `number` ; `displayValue`: `undefined` \| `number` \| `boolean` \| `PlanetTypeWeights` = value; `warning`: `undefined` = undefined } \| { `currentValue`: `undefined` \| `number` \| `boolean` \| `PlanetTypeWeights` = value; `defaultValue`: `number` ; `displayValue`: `undefined` \| `number` \| `boolean` \| `PlanetTypeWeights` = value; `warning`: `undefined` = undefined } \| { `currentValue`: `undefined` \| `number` \| `boolean` \| `PlanetTypeWeights` = value; `defaultValue`: `number` ; `displayValue`: `undefined` \| `number` \| `boolean` \| `PlanetTypeWeights` = value; `warning`: `undefined` = undefined } \| { `currentValue`: `undefined` \| `number` \| `boolean` \| `PlanetTypeWeights` = value; `defaultValue`: `number` ; `displayValue`: `undefined` \| `number` \| `boolean` \| `PlanetTypeWeights` = value; `warning`: `undefined` = undefined } \| { `currentValue`: `undefined` \| `number` \| `boolean` \| `PlanetTypeWeights` = value; `defaultValue`: `number` ; `displayValue`: `undefined` \| `number` \| `boolean` \| `PlanetTypeWeights` = value; `warning`: `undefined` = undefined } \| { `currentValue`: `undefined` \| `number` \| `boolean` \| `PlanetTypeWeights` = value; `defaultValue`: `number` ; `displayValue`: `undefined` \| `number` \| `boolean` \| `PlanetTypeWeights` = value; `warning`: `undefined` = undefined } \| { `currentValue`: `undefined` \| `number` \| `boolean` \| `PlanetTypeWeights` = value; `defaultValue`: `number` ; `displayValue`: `undefined` \| `number` \| `boolean` \| `PlanetTypeWeights` = value; `warning`: `undefined` = undefined } \| { `currentValue`: `undefined` \| `number` \| `boolean` \| `PlanetTypeWeights` = value; `defaultValue`: `number` ; `displayValue`: `undefined` \| `number` \| `boolean` \| `PlanetTypeWeights` = value; `warning`: `undefined` = undefined } \| { `currentValue`: `undefined` \| `number` \| `boolean` \| `PlanetTypeWeights` = value; `defaultValue`: `boolean` ; `displayValue`: `undefined` \| `number` \| `boolean` \| `PlanetTypeWeights` = value; `warning`: `undefined` = undefined } \| { `currentValue`: `undefined` \| `number` \| `boolean` \| `PlanetTypeWeights` = value; `defaultValue`: `number` ; `displayValue`: `undefined` \| `number` \| `boolean` \| `PlanetTypeWeights` = value; `warning`: `undefined` = undefined } \| { `currentValue`: `undefined` \| `number` \| `boolean` \| `PlanetTypeWeights` = value; `defaultValue`: `number` ; `displayValue`: `undefined` \| `number` \| `boolean` \| `PlanetTypeWeights` = value; `warning`: `undefined` = undefined } \| { `currentValue`: `undefined` \| `number` \| `boolean` \| `PlanetTypeWeights` = value; `defaultValue`: `number` ; `displayValue`: `undefined` \| `number` \| `boolean` \| `PlanetTypeWeights` = value; `warning`: `undefined` = undefined } \| { `currentValue`: `undefined` \| `number` \| `boolean` \| `PlanetTypeWeights` = value; `defaultValue`: `PlanetTypeWeights` ; `displayValue`: `undefined` \| `number` \| `boolean` \| `PlanetTypeWeights` = value; `warning`: `undefined` = undefined } \| { `currentValue`: `undefined` \| `number` \| `boolean` \| `PlanetTypeWeights` = value; `defaultValue`: `number` ; `displayValue`: `undefined` \| `number` \| `boolean` \| `PlanetTypeWeights` = value; `warning`: `undefined` = undefined } \| { `currentValue`: `undefined` \| `number` \| `boolean` \| `PlanetTypeWeights` = value; `defaultValue`: `Tuple6`<`number`\> ; `displayValue`: `undefined` \| `number` \| `boolean` \| `PlanetTypeWeights` = value; `warning`: `undefined` = undefined } \| { `currentValue`: `undefined` \| `number` \| `boolean` \| `PlanetTypeWeights` = value; `defaultValue`: `boolean` ; `displayValue`: `undefined` \| `number` \| `boolean` \| `PlanetTypeWeights` = value; `warning`: `undefined` = undefined } \| { `currentValue`: `undefined` \| `number` \| `boolean` \| `PlanetTypeWeights` = value; `defaultValue`: `number` ; `displayValue`: `undefined` \| `number` \| `boolean` \| `PlanetTypeWeights` = value; `warning`: `undefined` = undefined } \| { `currentValue`: `undefined` \| `number` \| `boolean` \| `PlanetTypeWeights` = value; `defaultValue`: `ExactArray10`<`number`\> ; `displayValue`: `undefined` \| `number` \| `boolean` \| `PlanetTypeWeights` = value; `warning`: `undefined` = undefined } \| { `currentValue`: `undefined` \| `number` \| `boolean` \| `PlanetTypeWeights` = value; `defaultValue`: `number` ; `displayValue`: `undefined` \| `number` \| `boolean` \| `PlanetTypeWeights` = value; `warning`: `undefined` = undefined } \| { `currentValue`: `undefined` \| `number` \| `boolean` \| `PlanetTypeWeights` = value; `defaultValue`: `number` ; `displayValue`: `undefined` \| `number` \| `boolean` \| `PlanetTypeWeights` = value; `warning`: `undefined` = undefined } \| { `currentValue`: `undefined` \| `number` \| `boolean` \| `PlanetTypeWeights` = value; `defaultValue`: `boolean` ; `displayValue`: `undefined` \| `number` \| `boolean` \| `PlanetTypeWeights` = value; `warning`: `undefined` = undefined } \| { `currentValue`: `undefined` \| `number` \| `boolean` \| `PlanetTypeWeights` = value; `defaultValue`: `number` ; `displayValue`: `undefined` \| `number` \| `boolean` \| `PlanetTypeWeights` = value; `warning`: `undefined` = undefined } \| { `currentValue`: `undefined` \| `number` \| `boolean` \| `PlanetTypeWeights` = value; `defaultValue`: `number` ; `displayValue`: `undefined` \| `number` \| `boolean` \| `PlanetTypeWeights` = value; `warning`: `undefined` = undefined } \| { `currentValue`: `undefined` \| `number` \| `boolean` \| `PlanetTypeWeights` = value; `defaultValue`: `number` ; `displayValue`: `undefined` \| `number` \| `boolean` \| `PlanetTypeWeights` = value; `warning`: `undefined` = undefined } \| { `currentValue`: `undefined` \| `number` \| `boolean` \| `PlanetTypeWeights` = value; `defaultValue`: `number` ; `displayValue`: `undefined` \| `number` \| `boolean` \| `PlanetTypeWeights` = value; `warning`: `undefined` = undefined } \| { `currentValue`: `undefined` \| `number` \| `boolean` \| `PlanetTypeWeights` = value; `defaultValue`: `undefined` \| `boolean` ; `displayValue`: `undefined` \| `number` \| `boolean` \| `PlanetTypeWeights` = value; `warning`: `undefined` = undefined } #### Parameters | Name | Type | | :------------------ | :------------------------------------------------------------------------- | | `__namedParameters` | [`LobbyConfigAction`](Frontend_Panes_Lobbies_Reducer.md#lobbyconfigaction) | | `state` | [`LobbyConfigState`](Frontend_Panes_Lobbies_Reducer.md#lobbyconfigstate) | #### Returns { `currentValue`: `undefined` \| `number` \| `boolean` \| `PlanetTypeWeights` = value; `defaultValue`: `ExactArray10`<`number`\> ; `displayValue`: `undefined` \| `number` \| `boolean` \| `PlanetTypeWeights` = value; `warning`: `undefined` = undefined } \| { `currentValue`: `undefined` \| `number` \| `boolean` \| `PlanetTypeWeights` = value; `defaultValue`: `ExactArray10`<`number`\> ; `displayValue`: `undefined` \| `number` \| `boolean` \| `PlanetTypeWeights` = value; `warning`: `undefined` = undefined } \| { `currentValue`: `undefined` \| `number` \| `boolean` \| `PlanetTypeWeights` = value; `defaultValue`: `boolean` ; `displayValue`: `undefined` \| `number` \| `boolean` \| `PlanetTypeWeights` = value; `warning`: `undefined` = undefined } \| { `currentValue`: `undefined` \| `number` \| `boolean` \| `PlanetTypeWeights` = value; `defaultValue`: `boolean` ; `displayValue`: `undefined` \| `number` \| `boolean` \| `PlanetTypeWeights` = value; `warning`: `undefined` = undefined } \| { `currentValue`: `undefined` \| `number` \| `boolean` \| `PlanetTypeWeights` = value; `defaultValue`: `number` ; `displayValue`: `undefined` \| `number` \| `boolean` \| `PlanetTypeWeights` = value; `warning`: `undefined` = undefined } \| { `currentValue`: `undefined` \| `number` \| `boolean` \| `PlanetTypeWeights` = value; `defaultValue`: `boolean` ; `displayValue`: `undefined` \| `number` \| `boolean` \| `PlanetTypeWeights` = value; `warning`: `undefined` = undefined } \| { `currentValue`: `undefined` \| `number` \| `boolean` \| `PlanetTypeWeights` = value; `defaultValue`: `number` ; `displayValue`: `undefined` \| `number` \| `boolean` \| `PlanetTypeWeights` = value; `warning`: `undefined` = undefined } \| { `currentValue`: `undefined` \| `number` \| `boolean` \| `PlanetTypeWeights` = value; `defaultValue`: `boolean` ; `displayValue`: `undefined` \| `number` \| `boolean` \| `PlanetTypeWeights` = value; `warning`: `undefined` = undefined } \| { `currentValue`: `undefined` \| `number` \| `boolean` \| `PlanetTypeWeights` = value; `defaultValue`: `number` ; `displayValue`: `undefined` \| `number` \| `boolean` \| `PlanetTypeWeights` = value; `warning`: `undefined` = undefined } \| { `currentValue`: `undefined` \| `number` \| `boolean` \| `PlanetTypeWeights` = value; `defaultValue`: `number` ; `displayValue`: `undefined` \| `number` \| `boolean` \| `PlanetTypeWeights` = value; `warning`: `undefined` = undefined } \| { `currentValue`: `undefined` \| `number` \| `boolean` \| `PlanetTypeWeights` = value; `defaultValue`: `number` ; `displayValue`: `undefined` \| `number` \| `boolean` \| `PlanetTypeWeights` = value; `warning`: `undefined` = undefined } \| { `currentValue`: `undefined` \| `number` \| `boolean` \| `PlanetTypeWeights` = value; `defaultValue`: `boolean` ; `displayValue`: `undefined` \| `number` \| `boolean` \| `PlanetTypeWeights` = value; `warning`: `undefined` = undefined } \| { `currentValue`: `undefined` \| `number` \| `boolean` \| `PlanetTypeWeights` = value; `defaultValue`: `boolean` ; `displayValue`: `undefined` \| `number` \| `boolean` \| `PlanetTypeWeights` = value; `warning`: `undefined` = undefined } \| { `currentValue`: `undefined` \| `number` \| `boolean` \| `PlanetTypeWeights` = value; `defaultValue`: `number` ; `displayValue`: `undefined` \| `number` \| `boolean` \| `PlanetTypeWeights` = value; `warning`: `undefined` = undefined } \| { `currentValue`: `undefined` \| `number` \| `boolean` \| `PlanetTypeWeights` = value; `defaultValue`: `number` ; `displayValue`: `undefined` \| `number` \| `boolean` \| `PlanetTypeWeights` = value; `warning`: `undefined` = undefined } \| { `currentValue`: `undefined` \| `number` \| `boolean` \| `PlanetTypeWeights` = value; `defaultValue`: `number` ; `displayValue`: `undefined` \| `number` \| `boolean` \| `PlanetTypeWeights` = value; `warning`: `undefined` = undefined } \| { `currentValue`: `undefined` \| `number` \| `boolean` \| `PlanetTypeWeights` = value; `defaultValue`: `number` ; `displayValue`: `undefined` \| `number` \| `boolean` \| `PlanetTypeWeights` = value; `warning`: `undefined` = undefined } \| { `currentValue`: `undefined` \| `number` \| `boolean` \| `PlanetTypeWeights` = value; `defaultValue`: `number` ; `displayValue`: `undefined` \| `number` \| `boolean` \| `PlanetTypeWeights` = value; `warning`: `undefined` = undefined } \| { `currentValue`: `undefined` \| `number` \| `boolean` \| `PlanetTypeWeights` = value; `defaultValue`: `number` ; `displayValue`: `undefined` \| `number` \| `boolean` \| `PlanetTypeWeights` = value; `warning`: `undefined` = undefined } \| { `currentValue`: `undefined` \| `number` \| `boolean` \| `PlanetTypeWeights` = value; `defaultValue`: `number` ; `displayValue`: `undefined` \| `number` \| `boolean` \| `PlanetTypeWeights` = value; `warning`: `undefined` = undefined } \| { `currentValue`: `undefined` \| `number` \| `boolean` \| `PlanetTypeWeights` = value; `defaultValue`: `number` ; `displayValue`: `undefined` \| `number` \| `boolean` \| `PlanetTypeWeights` = value; `warning`: `undefined` = undefined } \| { `currentValue`: `undefined` \| `number` \| `boolean` \| `PlanetTypeWeights` = value; `defaultValue`: `number` ; `displayValue`: `undefined` \| `number` \| `boolean` \| `PlanetTypeWeights` = value; `warning`: `undefined` = undefined } \| { `currentValue`: `undefined` \| `number` \| `boolean` \| `PlanetTypeWeights` = value; `defaultValue`: `number` ; `displayValue`: `undefined` \| `number` \| `boolean` \| `PlanetTypeWeights` = value; `warning`: `undefined` = undefined } \| { `currentValue`: `undefined` \| `number` \| `boolean` \| `PlanetTypeWeights` = value; `defaultValue`: `number` ; `displayValue`: `undefined` \| `number` \| `boolean` \| `PlanetTypeWeights` = value; `warning`: `undefined` = undefined } \| { `currentValue`: `undefined` \| `number` \| `boolean` \| `PlanetTypeWeights` = value; `defaultValue`: `boolean` ; `displayValue`: `undefined` \| `number` \| `boolean` \| `PlanetTypeWeights` = value; `warning`: `undefined` = undefined } \| { `currentValue`: `undefined` \| `number` \| `boolean` \| `PlanetTypeWeights` = value; `defaultValue`: `number` ; `displayValue`: `undefined` \| `number` \| `boolean` \| `PlanetTypeWeights` = value; `warning`: `undefined` = undefined } \| { `currentValue`: `undefined` \| `number` \| `boolean` \| `PlanetTypeWeights` = value; `defaultValue`: `number` ; `displayValue`: `undefined` \| `number` \| `boolean` \| `PlanetTypeWeights` = value; `warning`: `undefined` = undefined } \| { `currentValue`: `undefined` \| `number` \| `boolean` \| `PlanetTypeWeights` = value; `defaultValue`: `number` ; `displayValue`: `undefined` \| `number` \| `boolean` \| `PlanetTypeWeights` = value; `warning`: `undefined` = undefined } \| { `currentValue`: `undefined` \| `number` \| `boolean` \| `PlanetTypeWeights` = value; `defaultValue`: `PlanetTypeWeights` ; `displayValue`: `undefined` \| `number` \| `boolean` \| `PlanetTypeWeights` = value; `warning`: `undefined` = undefined } \| { `currentValue`: `undefined` \| `number` \| `boolean` \| `PlanetTypeWeights` = value; `defaultValue`: `number` ; `displayValue`: `undefined` \| `number` \| `boolean` \| `PlanetTypeWeights` = value; `warning`: `undefined` = undefined } \| { `currentValue`: `undefined` \| `number` \| `boolean` \| `PlanetTypeWeights` = value; `defaultValue`: `Tuple6`<`number`\> ; `displayValue`: `undefined` \| `number` \| `boolean` \| `PlanetTypeWeights` = value; `warning`: `undefined` = undefined } \| { `currentValue`: `undefined` \| `number` \| `boolean` \| `PlanetTypeWeights` = value; `defaultValue`: `boolean` ; `displayValue`: `undefined` \| `number` \| `boolean` \| `PlanetTypeWeights` = value; `warning`: `undefined` = undefined } \| { `currentValue`: `undefined` \| `number` \| `boolean` \| `PlanetTypeWeights` = value; `defaultValue`: `number` ; `displayValue`: `undefined` \| `number` \| `boolean` \| `PlanetTypeWeights` = value; `warning`: `undefined` = undefined } \| { `currentValue`: `undefined` \| `number` \| `boolean` \| `PlanetTypeWeights` = value; `defaultValue`: `ExactArray10`<`number`\> ; `displayValue`: `undefined` \| `number` \| `boolean` \| `PlanetTypeWeights` = value; `warning`: `undefined` = undefined } \| { `currentValue`: `undefined` \| `number` \| `boolean` \| `PlanetTypeWeights` = value; `defaultValue`: `number` ; `displayValue`: `undefined` \| `number` \| `boolean` \| `PlanetTypeWeights` = value; `warning`: `undefined` = undefined } \| { `currentValue`: `undefined` \| `number` \| `boolean` \| `PlanetTypeWeights` = value; `defaultValue`: `number` ; `displayValue`: `undefined` \| `number` \| `boolean` \| `PlanetTypeWeights` = value; `warning`: `undefined` = undefined } \| { `currentValue`: `undefined` \| `number` \| `boolean` \| `PlanetTypeWeights` = value; `defaultValue`: `boolean` ; `displayValue`: `undefined` \| `number` \| `boolean` \| `PlanetTypeWeights` = value; `warning`: `undefined` = undefined } \| { `currentValue`: `undefined` \| `number` \| `boolean` \| `PlanetTypeWeights` = value; `defaultValue`: `number` ; `displayValue`: `undefined` \| `number` \| `boolean` \| `PlanetTypeWeights` = value; `warning`: `undefined` = undefined } \| { `currentValue`: `undefined` \| `number` \| `boolean` \| `PlanetTypeWeights` = value; `defaultValue`: `number` ; `displayValue`: `undefined` \| `number` \| `boolean` \| `PlanetTypeWeights` = value; `warning`: `undefined` = undefined } \| { `currentValue`: `undefined` \| `number` \| `boolean` \| `PlanetTypeWeights` = value; `defaultValue`: `number` ; `displayValue`: `undefined` \| `number` \| `boolean` \| `PlanetTypeWeights` = value; `warning`: `undefined` = undefined } \| { `currentValue`: `undefined` \| `number` \| `boolean` \| `PlanetTypeWeights` = value; `defaultValue`: `number` ; `displayValue`: `undefined` \| `number` \| `boolean` \| `PlanetTypeWeights` = value; `warning`: `undefined` = undefined } \| { `currentValue`: `undefined` \| `number` \| `boolean` \| `PlanetTypeWeights` = value; `defaultValue`: `undefined` \| `boolean` ; `displayValue`: `undefined` \| `number` \| `boolean` \| `PlanetTypeWeights` = value; `warning`: `undefined` = undefined } --- ### ofArtifactPointValues ▸ **ofArtifactPointValues**(`__namedParameters`, `state`): { `currentValue`: `Tuple6`<`number`\> ; `defaultValue`: `Tuple6`<`number`\> ; `displayValue`: `undefined` \| [number?, number?, number?, number?, number?, number?] ; `warning`: `string` } \| { `currentValue`: `Tuple6`<`number`\> ; `defaultValue`: `Tuple6`<`number`\> ; `displayValue`: `undefined` \| [number?, number?, number?, number?, number?, number?] ; `warning`: `undefined` = undefined } \| { `currentValue`: `Tuple6`<`number`\> ; `defaultValue`: `Tuple6`<`number`\> ; `displayValue`: (`undefined` \| `number`)[] ; `warning`: `string` } \| { `currentValue`: `number`[] ; `defaultValue`: `Tuple6`<`number`\> ; `displayValue`: (`undefined` \| `number`)[] ; `warning`: `undefined` = undefined } #### Parameters | Name | Type | | :------------------------ | :----------------------------------------------------------------------- | | `__namedParameters` | `Object` | | `__namedParameters.index` | `number` | | `__namedParameters.type` | `"ARTIFACT_POINT_VALUES"` | | `__namedParameters.value` | `undefined` \| `number` | | `state` | [`LobbyConfigState`](Frontend_Panes_Lobbies_Reducer.md#lobbyconfigstate) | #### Returns { `currentValue`: `Tuple6`<`number`\> ; `defaultValue`: `Tuple6`<`number`\> ; `displayValue`: `undefined` \| [number?, number?, number?, number?, number?, number?] ; `warning`: `string` } \| { `currentValue`: `Tuple6`<`number`\> ; `defaultValue`: `Tuple6`<`number`\> ; `displayValue`: `undefined` \| [number?, number?, number?, number?, number?, number?] ; `warning`: `undefined` = undefined } \| { `currentValue`: `Tuple6`<`number`\> ; `defaultValue`: `Tuple6`<`number`\> ; `displayValue`: (`undefined` \| `number`)[] ; `warning`: `string` } \| { `currentValue`: `number`[] ; `defaultValue`: `Tuple6`<`number`\> ; `displayValue`: (`undefined` \| `number`)[] ; `warning`: `undefined` = undefined } --- ### ofBoolean ▸ **ofBoolean**(`__namedParameters`, `state`): { `currentValue`: `undefined` \| `boolean` ; `defaultValue`: `undefined` \| `boolean` ; `displayValue`: `undefined` = value; `warning`: `undefined` = undefined } \| { `currentValue`: `boolean` = value; `defaultValue`: `undefined` \| `boolean` ; `displayValue`: `boolean` = value; `warning`: `undefined` = undefined } #### Parameters | Name | Type | | :------------------ | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `__namedParameters` | { `type`: `"START_PAUSED"` ; `value`: `undefined` \| `boolean` } \| { `type`: `"ADMIN_CAN_ADD_PLANETS"` ; `value`: `undefined` \| `boolean` } \| { `type`: `"WORLD_RADIUS_LOCKED"` ; `value`: `undefined` \| `boolean` } \| { `type`: `"DISABLE_ZK_CHECKS"` ; `value`: `undefined` \| `boolean` } \| { `type`: `"PERLIN_MIRROR_X"` ; `value`: `undefined` \| `boolean` } \| { `type`: `"PERLIN_MIRROR_Y"` ; `value`: `undefined` \| `boolean` } \| { `type`: `"PLANET_TRANSFER_ENABLED"` ; `value`: `undefined` \| `boolean` } \| { `type`: `"SPACE_JUNK_ENABLED"` ; `value`: `undefined` \| `boolean` } \| { `type`: `"CAPTURE_ZONES_ENABLED"` ; `value`: `undefined` \| `boolean` } \| { `type`: `"WHITELIST_ENABLED"` ; `value`: `undefined` \| `boolean` } | | `state` | [`LobbyConfigState`](Frontend_Panes_Lobbies_Reducer.md#lobbyconfigstate) | #### Returns { `currentValue`: `undefined` \| `boolean` ; `defaultValue`: `undefined` \| `boolean` ; `displayValue`: `undefined` = value; `warning`: `undefined` = undefined } \| { `currentValue`: `boolean` = value; `defaultValue`: `undefined` \| `boolean` ; `displayValue`: `boolean` = value; `warning`: `undefined` = undefined } --- ### ofCaptureZoneChangeBlockInterval ▸ **ofCaptureZoneChangeBlockInterval**(`__namedParameters`, `state`): { `currentValue`: `number` ; `defaultValue`: `number` ; `displayValue`: `undefined` = value; `warning`: `undefined` = undefined } \| { `currentValue`: `number` ; `defaultValue`: `number` ; `displayValue`: `number` = value; `warning`: `string` } \| { `currentValue`: `number` = value; `defaultValue`: `number` ; `displayValue`: `number` = value; `warning`: `undefined` = undefined } #### Parameters | Name | Type | | :------------------------ | :----------------------------------------------------------------------- | | `__namedParameters` | `Object` | | `__namedParameters.type` | `"CAPTURE_ZONE_CHANGE_BLOCK_INTERVAL"` | | `__namedParameters.value` | `undefined` \| `number` | | `state` | [`LobbyConfigState`](Frontend_Panes_Lobbies_Reducer.md#lobbyconfigstate) | #### Returns { `currentValue`: `number` ; `defaultValue`: `number` ; `displayValue`: `undefined` = value; `warning`: `undefined` = undefined } \| { `currentValue`: `number` ; `defaultValue`: `number` ; `displayValue`: `number` = value; `warning`: `string` } \| { `currentValue`: `number` = value; `defaultValue`: `number` ; `displayValue`: `number` = value; `warning`: `undefined` = undefined } --- ### ofCaptureZonePlanetLevelScore ▸ **ofCaptureZonePlanetLevelScore**(`__namedParameters`, `state`): { `currentValue`: `ExactArray10`<`number`\> ; `defaultValue`: `ExactArray10`<`number`\> ; `displayValue`: `undefined` \| [number?, number?, number?, number?, number?, number?, number?, number?, number?, number?] ; `warning`: `string` } \| { `currentValue`: `ExactArray10`<`number`\> ; `defaultValue`: `ExactArray10`<`number`\> ; `displayValue`: `undefined` \| [number?, number?, number?, number?, number?, number?, number?, number?, number?, number?] ; `warning`: `undefined` = undefined } \| { `currentValue`: `ExactArray10`<`number`\> ; `defaultValue`: `ExactArray10`<`number`\> ; `displayValue`: (`undefined` \| `number`)[] ; `warning`: `string` } \| { `currentValue`: `number`[] ; `defaultValue`: `ExactArray10`<`number`\> ; `displayValue`: (`undefined` \| `number`)[] ; `warning`: `undefined` = undefined } #### Parameters | Name | Type | | :------------------------ | :----------------------------------------------------------------------- | | `__namedParameters` | `Object` | | `__namedParameters.index` | `number` | | `__namedParameters.type` | `"CAPTURE_ZONE_PLANET_LEVEL_SCORE"` | | `__namedParameters.value` | `undefined` \| `number` | | `state` | [`LobbyConfigState`](Frontend_Panes_Lobbies_Reducer.md#lobbyconfigstate) | #### Returns { `currentValue`: `ExactArray10`<`number`\> ; `defaultValue`: `ExactArray10`<`number`\> ; `displayValue`: `undefined` \| [number?, number?, number?, number?, number?, number?, number?, number?, number?, number?] ; `warning`: `string` } \| { `currentValue`: `ExactArray10`<`number`\> ; `defaultValue`: `ExactArray10`<`number`\> ; `displayValue`: `undefined` \| [number?, number?, number?, number?, number?, number?, number?, number?, number?, number?] ; `warning`: `undefined` = undefined } \| { `currentValue`: `ExactArray10`<`number`\> ; `defaultValue`: `ExactArray10`<`number`\> ; `displayValue`: (`undefined` \| `number`)[] ; `warning`: `string` } \| { `currentValue`: `number`[] ; `defaultValue`: `ExactArray10`<`number`\> ; `displayValue`: (`undefined` \| `number`)[] ; `warning`: `undefined` = undefined } --- ### ofCaptureZoneRadius ▸ **ofCaptureZoneRadius**(`__namedParameters`, `state`): { `currentValue`: `number` ; `defaultValue`: `number` ; `displayValue`: `undefined` = value; `warning`: `undefined` = undefined } \| { `currentValue`: `number` ; `defaultValue`: `number` ; `displayValue`: `number` = value; `warning`: `string` } \| { `currentValue`: `number` = value; `defaultValue`: `number` ; `displayValue`: `number` = value; `warning`: `undefined` = undefined } #### Parameters | Name | Type | | :------------------------ | :----------------------------------------------------------------------- | | `__namedParameters` | `Object` | | `__namedParameters.type` | `"CAPTURE_ZONE_RADIUS"` | | `__namedParameters.value` | `undefined` \| `number` | | `state` | [`LobbyConfigState`](Frontend_Panes_Lobbies_Reducer.md#lobbyconfigstate) | #### Returns { `currentValue`: `number` ; `defaultValue`: `number` ; `displayValue`: `undefined` = value; `warning`: `undefined` = undefined } \| { `currentValue`: `number` ; `defaultValue`: `number` ; `displayValue`: `number` = value; `warning`: `string` } \| { `currentValue`: `number` = value; `defaultValue`: `number` ; `displayValue`: `number` = value; `warning`: `undefined` = undefined } --- ### ofCaptureZonesPer5000WorldRadius ▸ **ofCaptureZonesPer5000WorldRadius**(`__namedParameters`, `state`): { `currentValue`: `number` ; `defaultValue`: `number` ; `displayValue`: `undefined` = value; `warning`: `undefined` = undefined } \| { `currentValue`: `number` ; `defaultValue`: `number` ; `displayValue`: `number` = value; `warning`: `string` } \| { `currentValue`: `number` = value; `defaultValue`: `number` ; `displayValue`: `number` = value; `warning`: `undefined` = undefined } #### Parameters | Name | Type | | :------------------------ | :----------------------------------------------------------------------- | | `__namedParameters` | `Object` | | `__namedParameters.type` | `"CAPTURE_ZONES_PER_5000_WORLD_RADIUS"` | | `__namedParameters.value` | `undefined` \| `number` | | `state` | [`LobbyConfigState`](Frontend_Panes_Lobbies_Reducer.md#lobbyconfigstate) | #### Returns { `currentValue`: `number` ; `defaultValue`: `number` ; `displayValue`: `undefined` = value; `warning`: `undefined` = undefined } \| { `currentValue`: `number` ; `defaultValue`: `number` ; `displayValue`: `number` = value; `warning`: `string` } \| { `currentValue`: `number` = value; `defaultValue`: `number` ; `displayValue`: `number` = value; `warning`: `undefined` = undefined } --- ### ofMaxNaturalPlanetLevel ▸ **ofMaxNaturalPlanetLevel**(`__namedParameters`, `state`): { `currentValue`: `number` ; `defaultValue`: `number` ; `displayValue`: `undefined` = value; `warning`: `undefined` = undefined } \| { `currentValue`: `number` ; `defaultValue`: `number` ; `displayValue`: `number` = value; `warning`: `string` } \| { `currentValue`: `number` = value; `defaultValue`: `number` ; `displayValue`: `number` = value; `warning`: `undefined` = undefined } #### Parameters | Name | Type | | :------------------------ | :----------------------------------------------------------------------- | | `__namedParameters` | `Object` | | `__namedParameters.type` | `"MAX_NATURAL_PLANET_LEVEL"` | | `__namedParameters.value` | `undefined` \| `number` | | `state` | [`LobbyConfigState`](Frontend_Panes_Lobbies_Reducer.md#lobbyconfigstate) | #### Returns { `currentValue`: `number` ; `defaultValue`: `number` ; `displayValue`: `undefined` = value; `warning`: `undefined` = undefined } \| { `currentValue`: `number` ; `defaultValue`: `number` ; `displayValue`: `number` = value; `warning`: `string` } \| { `currentValue`: `number` = value; `defaultValue`: `number` ; `displayValue`: `number` = value; `warning`: `undefined` = undefined } --- ### ofNoop ▸ **ofNoop**(`__namedParameters`, `state`): { `currentValue`: `ExactArray10`<`number`\> ; `defaultValue`: `ExactArray10`<`number`\> ; `displayValue`: `undefined` \| [number?, number?, number?, number?, number?, number?, number?, number?, number?, number?] ; `warning`: `undefined` \| `string` } \| { `currentValue`: `ExactArray10`<`number`\> ; `defaultValue`: `ExactArray10`<`number`\> ; `displayValue`: `undefined` \| [number?, number?, number?, number?, number?, number?, number?, number?, number?, number?] ; `warning`: `undefined` \| `string` } \| { `currentValue`: `boolean` ; `defaultValue`: `boolean` ; `displayValue`: `undefined` \| `Partial`<`boolean`\> ; `warning`: `undefined` \| `string` } \| { `currentValue`: `boolean` ; `defaultValue`: `boolean` ; `displayValue`: `undefined` \| `Partial`<`boolean`\> ; `warning`: `undefined` \| `string` } \| { `currentValue`: `number` ; `defaultValue`: `number` ; `displayValue`: `undefined` \| `number` ; `warning`: `undefined` \| `string` } \| { `currentValue`: `boolean` ; `defaultValue`: `boolean` ; `displayValue`: `undefined` \| `Partial`<`boolean`\> ; `warning`: `undefined` \| `string` } \| { `currentValue`: `number` ; `defaultValue`: `number` ; `displayValue`: `undefined` \| `number` ; `warning`: `undefined` \| `string` } \| { `currentValue`: `boolean` ; `defaultValue`: `boolean` ; `displayValue`: `undefined` \| `Partial`<`boolean`\> ; `warning`: `undefined` \| `string` } \| { `currentValue`: `number` ; `defaultValue`: `number` ; `displayValue`: `undefined` \| `number` ; `warning`: `undefined` \| `string` } \| { `currentValue`: `number` ; `defaultValue`: `number` ; `displayValue`: `undefined` \| `number` ; `warning`: `undefined` \| `string` } \| { `currentValue`: `number` ; `defaultValue`: `number` ; `displayValue`: `undefined` \| `number` ; `warning`: `undefined` \| `string` } \| { `currentValue`: `boolean` ; `defaultValue`: `boolean` ; `displayValue`: `undefined` \| `Partial`<`boolean`\> ; `warning`: `undefined` \| `string` } \| { `currentValue`: `boolean` ; `defaultValue`: `boolean` ; `displayValue`: `undefined` \| `Partial`<`boolean`\> ; `warning`: `undefined` \| `string` } \| { `currentValue`: `number` ; `defaultValue`: `number` ; `displayValue`: `undefined` \| `number` ; `warning`: `undefined` \| `string` } \| { `currentValue`: `number` ; `defaultValue`: `number` ; `displayValue`: `undefined` \| `number` ; `warning`: `undefined` \| `string` } \| { `currentValue`: `number` ; `defaultValue`: `number` ; `displayValue`: `undefined` \| `number` ; `warning`: `undefined` \| `string` } \| { `currentValue`: `number` ; `defaultValue`: `number` ; `displayValue`: `undefined` \| `number` ; `warning`: `undefined` \| `string` } \| { `currentValue`: `number` ; `defaultValue`: `number` ; `displayValue`: `undefined` \| `number` ; `warning`: `undefined` \| `string` } \| { `currentValue`: `number` ; `defaultValue`: `number` ; `displayValue`: `undefined` \| `number` ; `warning`: `undefined` \| `string` } \| { `currentValue`: `number` ; `defaultValue`: `number` ; `displayValue`: `undefined` \| `number` ; `warning`: `undefined` \| `string` } \| { `currentValue`: `number` ; `defaultValue`: `number` ; `displayValue`: `undefined` \| `number` ; `warning`: `undefined` \| `string` } \| { `currentValue`: `number` ; `defaultValue`: `number` ; `displayValue`: `undefined` \| `number` ; `warning`: `undefined` \| `string` } \| { `currentValue`: `number` ; `defaultValue`: `number` ; `displayValue`: `undefined` \| `number` ; `warning`: `undefined` \| `string` } \| { `currentValue`: `number` ; `defaultValue`: `number` ; `displayValue`: `undefined` \| `number` ; `warning`: `undefined` \| `string` } \| { `currentValue`: `boolean` ; `defaultValue`: `boolean` ; `displayValue`: `undefined` \| `Partial`<`boolean`\> ; `warning`: `undefined` \| `string` } \| { `currentValue`: `number` ; `defaultValue`: `number` ; `displayValue`: `undefined` \| `number` ; `warning`: `undefined` \| `string` } \| { `currentValue`: `number` ; `defaultValue`: `number` ; `displayValue`: `undefined` \| `number` ; `warning`: `undefined` \| `string` } \| { `currentValue`: `number` ; `defaultValue`: `number` ; `displayValue`: `undefined` \| `number` ; `warning`: `undefined` \| `string` } \| { `currentValue`: `PlanetTypeWeights` ; `defaultValue`: `PlanetTypeWeights` ; `displayValue`: `undefined` \| [ExactArray10\>?, ExactArray10\>?, ExactArray10\>?, ExactArray10\>?] ; `warning`: `undefined` \| `string` } \| { `currentValue`: `number` ; `defaultValue`: `number` ; `displayValue`: `undefined` \| `number` ; `warning`: `undefined` \| `string` } \| { `currentValue`: `Tuple6`<`number`\> ; `defaultValue`: `Tuple6`<`number`\> ; `displayValue`: `undefined` \| [number?, number?, number?, number?, number?, number?] ; `warning`: `undefined` \| `string` } \| { `currentValue`: `boolean` ; `defaultValue`: `boolean` ; `displayValue`: `undefined` \| `Partial`<`boolean`\> ; `warning`: `undefined` \| `string` } \| { `currentValue`: `number` ; `defaultValue`: `number` ; `displayValue`: `undefined` \| `number` ; `warning`: `undefined` \| `string` } \| { `currentValue`: `ExactArray10`<`number`\> ; `defaultValue`: `ExactArray10`<`number`\> ; `displayValue`: `undefined` \| [number?, number?, number?, number?, number?, number?, number?, number?, number?, number?] ; `warning`: `undefined` \| `string` } \| { `currentValue`: `number` ; `defaultValue`: `number` ; `displayValue`: `undefined` \| `number` ; `warning`: `undefined` \| `string` } \| { `currentValue`: `number` ; `defaultValue`: `number` ; `displayValue`: `undefined` \| `number` ; `warning`: `undefined` \| `string` } \| { `currentValue`: `boolean` ; `defaultValue`: `boolean` ; `displayValue`: `undefined` \| `Partial`<`boolean`\> ; `warning`: `undefined` \| `string` } \| { `currentValue`: `number` ; `defaultValue`: `number` ; `displayValue`: `undefined` \| `number` ; `warning`: `undefined` \| `string` } \| { `currentValue`: `number` ; `defaultValue`: `number` ; `displayValue`: `undefined` \| `number` ; `warning`: `undefined` \| `string` } \| { `currentValue`: `number` ; `defaultValue`: `number` ; `displayValue`: `undefined` \| `number` ; `warning`: `undefined` \| `string` } \| { `currentValue`: `number` ; `defaultValue`: `number` ; `displayValue`: `undefined` \| `number` ; `warning`: `undefined` \| `string` } \| { `currentValue`: `undefined` \| `boolean` ; `defaultValue`: `undefined` \| `boolean` ; `displayValue`: `Partial`<`undefined` \| `boolean`\> ; `warning`: `undefined` \| `string` } #### Parameters | Name | Type | | :------------------ | :------------------------------------------------------------------------- | | `__namedParameters` | [`LobbyConfigAction`](Frontend_Panes_Lobbies_Reducer.md#lobbyconfigaction) | | `state` | [`LobbyConfigState`](Frontend_Panes_Lobbies_Reducer.md#lobbyconfigstate) | #### Returns { `currentValue`: `ExactArray10`<`number`\> ; `defaultValue`: `ExactArray10`<`number`\> ; `displayValue`: `undefined` \| [number?, number?, number?, number?, number?, number?, number?, number?, number?, number?] ; `warning`: `undefined` \| `string` } \| { `currentValue`: `ExactArray10`<`number`\> ; `defaultValue`: `ExactArray10`<`number`\> ; `displayValue`: `undefined` \| [number?, number?, number?, number?, number?, number?, number?, number?, number?, number?] ; `warning`: `undefined` \| `string` } \| { `currentValue`: `boolean` ; `defaultValue`: `boolean` ; `displayValue`: `undefined` \| `Partial`<`boolean`\> ; `warning`: `undefined` \| `string` } \| { `currentValue`: `boolean` ; `defaultValue`: `boolean` ; `displayValue`: `undefined` \| `Partial`<`boolean`\> ; `warning`: `undefined` \| `string` } \| { `currentValue`: `number` ; `defaultValue`: `number` ; `displayValue`: `undefined` \| `number` ; `warning`: `undefined` \| `string` } \| { `currentValue`: `boolean` ; `defaultValue`: `boolean` ; `displayValue`: `undefined` \| `Partial`<`boolean`\> ; `warning`: `undefined` \| `string` } \| { `currentValue`: `number` ; `defaultValue`: `number` ; `displayValue`: `undefined` \| `number` ; `warning`: `undefined` \| `string` } \| { `currentValue`: `boolean` ; `defaultValue`: `boolean` ; `displayValue`: `undefined` \| `Partial`<`boolean`\> ; `warning`: `undefined` \| `string` } \| { `currentValue`: `number` ; `defaultValue`: `number` ; `displayValue`: `undefined` \| `number` ; `warning`: `undefined` \| `string` } \| { `currentValue`: `number` ; `defaultValue`: `number` ; `displayValue`: `undefined` \| `number` ; `warning`: `undefined` \| `string` } \| { `currentValue`: `number` ; `defaultValue`: `number` ; `displayValue`: `undefined` \| `number` ; `warning`: `undefined` \| `string` } \| { `currentValue`: `boolean` ; `defaultValue`: `boolean` ; `displayValue`: `undefined` \| `Partial`<`boolean`\> ; `warning`: `undefined` \| `string` } \| { `currentValue`: `boolean` ; `defaultValue`: `boolean` ; `displayValue`: `undefined` \| `Partial`<`boolean`\> ; `warning`: `undefined` \| `string` } \| { `currentValue`: `number` ; `defaultValue`: `number` ; `displayValue`: `undefined` \| `number` ; `warning`: `undefined` \| `string` } \| { `currentValue`: `number` ; `defaultValue`: `number` ; `displayValue`: `undefined` \| `number` ; `warning`: `undefined` \| `string` } \| { `currentValue`: `number` ; `defaultValue`: `number` ; `displayValue`: `undefined` \| `number` ; `warning`: `undefined` \| `string` } \| { `currentValue`: `number` ; `defaultValue`: `number` ; `displayValue`: `undefined` \| `number` ; `warning`: `undefined` \| `string` } \| { `currentValue`: `number` ; `defaultValue`: `number` ; `displayValue`: `undefined` \| `number` ; `warning`: `undefined` \| `string` } \| { `currentValue`: `number` ; `defaultValue`: `number` ; `displayValue`: `undefined` \| `number` ; `warning`: `undefined` \| `string` } \| { `currentValue`: `number` ; `defaultValue`: `number` ; `displayValue`: `undefined` \| `number` ; `warning`: `undefined` \| `string` } \| { `currentValue`: `number` ; `defaultValue`: `number` ; `displayValue`: `undefined` \| `number` ; `warning`: `undefined` \| `string` } \| { `currentValue`: `number` ; `defaultValue`: `number` ; `displayValue`: `undefined` \| `number` ; `warning`: `undefined` \| `string` } \| { `currentValue`: `number` ; `defaultValue`: `number` ; `displayValue`: `undefined` \| `number` ; `warning`: `undefined` \| `string` } \| { `currentValue`: `number` ; `defaultValue`: `number` ; `displayValue`: `undefined` \| `number` ; `warning`: `undefined` \| `string` } \| { `currentValue`: `boolean` ; `defaultValue`: `boolean` ; `displayValue`: `undefined` \| `Partial`<`boolean`\> ; `warning`: `undefined` \| `string` } \| { `currentValue`: `number` ; `defaultValue`: `number` ; `displayValue`: `undefined` \| `number` ; `warning`: `undefined` \| `string` } \| { `currentValue`: `number` ; `defaultValue`: `number` ; `displayValue`: `undefined` \| `number` ; `warning`: `undefined` \| `string` } \| { `currentValue`: `number` ; `defaultValue`: `number` ; `displayValue`: `undefined` \| `number` ; `warning`: `undefined` \| `string` } \| { `currentValue`: `PlanetTypeWeights` ; `defaultValue`: `PlanetTypeWeights` ; `displayValue`: `undefined` \| [ExactArray10\>?, ExactArray10\>?, ExactArray10\>?, ExactArray10\>?] ; `warning`: `undefined` \| `string` } \| { `currentValue`: `number` ; `defaultValue`: `number` ; `displayValue`: `undefined` \| `number` ; `warning`: `undefined` \| `string` } \| { `currentValue`: `Tuple6`<`number`\> ; `defaultValue`: `Tuple6`<`number`\> ; `displayValue`: `undefined` \| [number?, number?, number?, number?, number?, number?] ; `warning`: `undefined` \| `string` } \| { `currentValue`: `boolean` ; `defaultValue`: `boolean` ; `displayValue`: `undefined` \| `Partial`<`boolean`\> ; `warning`: `undefined` \| `string` } \| { `currentValue`: `number` ; `defaultValue`: `number` ; `displayValue`: `undefined` \| `number` ; `warning`: `undefined` \| `string` } \| { `currentValue`: `ExactArray10`<`number`\> ; `defaultValue`: `ExactArray10`<`number`\> ; `displayValue`: `undefined` \| [number?, number?, number?, number?, number?, number?, number?, number?, number?, number?] ; `warning`: `undefined` \| `string` } \| { `currentValue`: `number` ; `defaultValue`: `number` ; `displayValue`: `undefined` \| `number` ; `warning`: `undefined` \| `string` } \| { `currentValue`: `number` ; `defaultValue`: `number` ; `displayValue`: `undefined` \| `number` ; `warning`: `undefined` \| `string` } \| { `currentValue`: `boolean` ; `defaultValue`: `boolean` ; `displayValue`: `undefined` \| `Partial`<`boolean`\> ; `warning`: `undefined` \| `string` } \| { `currentValue`: `number` ; `defaultValue`: `number` ; `displayValue`: `undefined` \| `number` ; `warning`: `undefined` \| `string` } \| { `currentValue`: `number` ; `defaultValue`: `number` ; `displayValue`: `undefined` \| `number` ; `warning`: `undefined` \| `string` } \| { `currentValue`: `number` ; `defaultValue`: `number` ; `displayValue`: `undefined` \| `number` ; `warning`: `undefined` \| `string` } \| { `currentValue`: `number` ; `defaultValue`: `number` ; `displayValue`: `undefined` \| `number` ; `warning`: `undefined` \| `string` } \| { `currentValue`: `undefined` \| `boolean` ; `defaultValue`: `undefined` \| `boolean` ; `displayValue`: `Partial`<`undefined` \| `boolean`\> ; `warning`: `undefined` \| `string` } --- ### ofPerlinLengthScale ▸ **ofPerlinLengthScale**(`__namedParameters`, `state`): { `currentValue`: `number` ; `defaultValue`: `number` ; `displayValue`: `undefined` = value; `warning`: `undefined` = undefined } \| { `currentValue`: `number` ; `defaultValue`: `number` ; `displayValue`: `never` = value; `warning`: `string` = 'Value must be a number' } \| { `currentValue`: `number` ; `defaultValue`: `number` ; `displayValue`: `number` = value; `warning`: `undefined` = undefined } #### Parameters | Name | Type | | :------------------------ | :----------------------------------------------------------------------- | | `__namedParameters` | `Object` | | `__namedParameters.type` | `"PERLIN_LENGTH_SCALE"` | | `__namedParameters.value` | `undefined` \| `number` | | `state` | [`LobbyConfigState`](Frontend_Panes_Lobbies_Reducer.md#lobbyconfigstate) | #### Returns { `currentValue`: `number` ; `defaultValue`: `number` ; `displayValue`: `undefined` = value; `warning`: `undefined` = undefined } \| { `currentValue`: `number` ; `defaultValue`: `number` ; `displayValue`: `never` = value; `warning`: `string` = 'Value must be a number' } \| { `currentValue`: `number` ; `defaultValue`: `number` ; `displayValue`: `number` = value; `warning`: `undefined` = undefined } --- ### ofPlanetLevelJunk ▸ **ofPlanetLevelJunk**(`__namedParameters`, `state`): { `currentValue`: `ExactArray10`<`number`\> ; `defaultValue`: `ExactArray10`<`number`\> ; `displayValue`: `undefined` \| [number?, number?, number?, number?, number?, number?, number?, number?, number?, number?] ; `warning`: `string` } \| { `currentValue`: `ExactArray10`<`number`\> ; `defaultValue`: `ExactArray10`<`number`\> ; `displayValue`: `undefined` \| [number?, number?, number?, number?, number?, number?, number?, number?, number?, number?] ; `warning`: `undefined` = undefined } \| { `currentValue`: `ExactArray10`<`number`\> ; `defaultValue`: `ExactArray10`<`number`\> ; `displayValue`: (`undefined` \| `number`)[] ; `warning`: `string` } \| { `currentValue`: `number`[] ; `displayValue`: (`undefined` \| `number`)[] ; `warning`: `string` } \| { `currentValue`: `number`[] ; `defaultValue`: `ExactArray10`<`number`\> ; `displayValue`: (`undefined` \| `number`)[] ; `warning`: `undefined` = undefined } #### Parameters | Name | Type | | :------------------------ | :----------------------------------------------------------------------- | | `__namedParameters` | `Object` | | `__namedParameters.index` | `number` | | `__namedParameters.type` | `"PLANET_LEVEL_JUNK"` | | `__namedParameters.value` | `undefined` \| `number` | | `state` | [`LobbyConfigState`](Frontend_Panes_Lobbies_Reducer.md#lobbyconfigstate) | #### Returns { `currentValue`: `ExactArray10`<`number`\> ; `defaultValue`: `ExactArray10`<`number`\> ; `displayValue`: `undefined` \| [number?, number?, number?, number?, number?, number?, number?, number?, number?, number?] ; `warning`: `string` } \| { `currentValue`: `ExactArray10`<`number`\> ; `defaultValue`: `ExactArray10`<`number`\> ; `displayValue`: `undefined` \| [number?, number?, number?, number?, number?, number?, number?, number?, number?, number?] ; `warning`: `undefined` = undefined } \| { `currentValue`: `ExactArray10`<`number`\> ; `defaultValue`: `ExactArray10`<`number`\> ; `displayValue`: (`undefined` \| `number`)[] ; `warning`: `string` } \| { `currentValue`: `number`[] ; `displayValue`: (`undefined` \| `number`)[] ; `warning`: `string` } \| { `currentValue`: `number`[] ; `defaultValue`: `ExactArray10`<`number`\> ; `displayValue`: (`undefined` \| `number`)[] ; `warning`: `undefined` = undefined } --- ### ofPlanetLevelThresholds ▸ **ofPlanetLevelThresholds**(`__namedParameters`, `state`): { `currentValue`: `ExactArray10`<`number`\> ; `defaultValue`: `ExactArray10`<`number`\> ; `displayValue`: `undefined` \| [number?, number?, number?, number?, number?, number?, number?, number?, number?, number?] ; `warning`: `string` } \| { `currentValue`: `ExactArray10`<`number`\> ; `defaultValue`: `ExactArray10`<`number`\> ; `displayValue`: `undefined` \| [number?, number?, number?, number?, number?, number?, number?, number?, number?, number?] ; `warning`: `undefined` = undefined } \| { `currentValue`: `number`[] ; `defaultValue`: `ExactArray10`<`number`\> ; `displayValue`: (`undefined` \| `number`)[] ; `warning`: `undefined` = undefined } \| { `currentValue`: `ExactArray10`<`number`\> ; `defaultValue`: `ExactArray10`<`number`\> ; `displayValue`: (`undefined` \| `number`)[] ; `warning`: `string` } \| { `currentValue`: `number`[] ; `displayValue`: (`undefined` \| `number`)[] ; `warning`: `string` } #### Parameters | Name | Type | | :------------------------ | :----------------------------------------------------------------------- | | `__namedParameters` | `Object` | | `__namedParameters.index` | `number` | | `__namedParameters.type` | `"PLANET_LEVEL_THRESHOLDS"` | | `__namedParameters.value` | `undefined` \| `number` | | `state` | [`LobbyConfigState`](Frontend_Panes_Lobbies_Reducer.md#lobbyconfigstate) | #### Returns { `currentValue`: `ExactArray10`<`number`\> ; `defaultValue`: `ExactArray10`<`number`\> ; `displayValue`: `undefined` \| [number?, number?, number?, number?, number?, number?, number?, number?, number?, number?] ; `warning`: `string` } \| { `currentValue`: `ExactArray10`<`number`\> ; `defaultValue`: `ExactArray10`<`number`\> ; `displayValue`: `undefined` \| [number?, number?, number?, number?, number?, number?, number?, number?, number?, number?] ; `warning`: `undefined` = undefined } \| { `currentValue`: `number`[] ; `defaultValue`: `ExactArray10`<`number`\> ; `displayValue`: (`undefined` \| `number`)[] ; `warning`: `undefined` = undefined } \| { `currentValue`: `ExactArray10`<`number`\> ; `defaultValue`: `ExactArray10`<`number`\> ; `displayValue`: (`undefined` \| `number`)[] ; `warning`: `string` } \| { `currentValue`: `number`[] ; `displayValue`: (`undefined` \| `number`)[] ; `warning`: `string` } --- ### ofPlanetRarity ▸ **ofPlanetRarity**(`__namedParameters`, `state`): { `currentValue`: `number` ; `defaultValue`: `number` ; `displayValue`: `undefined` = value; `warning`: `undefined` = undefined } \| { `currentValue`: `number` ; `defaultValue`: `number` ; `displayValue`: `number` = value; `warning`: `string` } \| { `currentValue`: `number` = value; `defaultValue`: `number` ; `displayValue`: `number` = value; `warning`: `undefined` = undefined } #### Parameters | Name | Type | | :------------------------ | :----------------------------------------------------------------------- | | `__namedParameters` | `Object` | | `__namedParameters.type` | `"PLANET_RARITY"` | | `__namedParameters.value` | `undefined` \| `number` | | `state` | [`LobbyConfigState`](Frontend_Panes_Lobbies_Reducer.md#lobbyconfigstate) | #### Returns { `currentValue`: `number` ; `defaultValue`: `number` ; `displayValue`: `undefined` = value; `warning`: `undefined` = undefined } \| { `currentValue`: `number` ; `defaultValue`: `number` ; `displayValue`: `number` = value; `warning`: `string` } \| { `currentValue`: `number` = value; `defaultValue`: `number` ; `displayValue`: `number` = value; `warning`: `undefined` = undefined } --- ### ofPositiveFloat ▸ **ofPositiveFloat**(`__namedParameters`, `state`): { `currentValue`: `ExactArray10`<`number`\> ; `defaultValue`: `ExactArray10`<`number`\> ; `displayValue`: `undefined` = value; `warning`: `undefined` = undefined } \| { `currentValue`: `number` ; `defaultValue`: `number` ; `displayValue`: `undefined` = value; `warning`: `undefined` = undefined } \| { `currentValue`: `Tuple6`<`number`\> ; `defaultValue`: `Tuple6`<`number`\> ; `displayValue`: `undefined` = value; `warning`: `undefined` = undefined } \| { `currentValue`: `ExactArray10`<`number`\> ; `defaultValue`: `ExactArray10`<`number`\> ; `displayValue`: `number` = value; `warning`: `string` } \| { `currentValue`: `number` ; `defaultValue`: `number` ; `displayValue`: `number` = value; `warning`: `string` } \| { `currentValue`: `Tuple6`<`number`\> ; `defaultValue`: `Tuple6`<`number`\> ; `displayValue`: `number` = value; `warning`: `string` } \| { `currentValue`: `number` = value; `defaultValue`: `ExactArray10`<`number`\> ; `displayValue`: `number` = value; `warning`: `undefined` = undefined } \| { `currentValue`: `number` = value; `defaultValue`: `number` ; `displayValue`: `number` = value; `warning`: `undefined` = undefined } \| { `currentValue`: `number` = value; `defaultValue`: `Tuple6`<`number`\> ; `displayValue`: `number` = value; `warning`: `undefined` = undefined } #### Parameters | Name | Type | | :------------------ | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `__namedParameters` | { `type`: `"TOKEN_MINT_END_TIMESTAMP"` ; `value`: `undefined` \| `number` } \| { `type`: `"WORLD_RADIUS_MIN"` ; `value`: `undefined` \| `number` } \| { `type`: `"PLANETHASH_KEY"` ; `value`: `undefined` \| `number` } \| { `type`: `"SPACETYPE_KEY"` ; `value`: `undefined` \| `number` } \| { `type`: `"BIOMEBASE_KEY"` ; `value`: `undefined` \| `number` } \| { `type`: `"PERLIN_LENGTH_SCALE"` ; `value`: `undefined` \| `number` } \| { `type`: `"MAX_NATURAL_PLANET_LEVEL"` ; `value`: `undefined` \| `number` } \| { `type`: `"TIME_FACTOR_HUNDREDTHS"` ; `value`: `undefined` \| `number` } \| { `type`: `"PERLIN_THRESHOLD_1"` ; `value`: `undefined` \| `number` } \| { `type`: `"PERLIN_THRESHOLD_2"` ; `value`: `undefined` \| `number` } \| { `type`: `"PERLIN_THRESHOLD_3"` ; `value`: `undefined` \| `number` } \| { `type`: `"INIT_PERLIN_MIN"` ; `value`: `undefined` \| `number` } \| { `type`: `"INIT_PERLIN_MAX"` ; `value`: `undefined` \| `number` } \| { `type`: `"BIOME_THRESHOLD_1"` ; `value`: `undefined` \| `number` } \| { `type`: `"BIOME_THRESHOLD_2"` ; `value`: `undefined` \| `number` } \| { `index`: `number` ; `type`: `"PLANET_LEVEL_THRESHOLDS"` ; `value`: `undefined` \| `number` } \| { `type`: `"PLANET_RARITY"` ; `value`: `undefined` \| `number` } \| { `type`: `"PHOTOID_ACTIVATION_DELAY"` ; `value`: `undefined` \| `number` } \| { `type`: `"SPAWN_RIM_AREA"` ; `value`: `undefined` \| `number` } \| { `type`: `"LOCATION_REVEAL_COOLDOWN"` ; `value`: `undefined` \| `number` } \| { `type`: `"SILVER_SCORE_VALUE"` ; `value`: `undefined` \| `number` } \| { `index`: `number` ; `type`: `"ARTIFACT_POINT_VALUES"` ; `value`: `undefined` \| `number` } \| { `type`: `"SPACE_JUNK_LIMIT"` ; `value`: `undefined` \| `number` } \| { `index`: `number` ; `type`: `"PLANET_LEVEL_JUNK"` ; `value`: `undefined` \| `number` } \| { `type`: `"ABANDON_SPEED_CHANGE_PERCENT"` ; `value`: `undefined` \| `number` } \| { `type`: `"ABANDON_RANGE_CHANGE_PERCENT"` ; `value`: `undefined` \| `number` } \| { `type`: `"CAPTURE_ZONE_CHANGE_BLOCK_INTERVAL"` ; `value`: `undefined` \| `number` } \| { `type`: `"CAPTURE_ZONE_RADIUS"` ; `value`: `undefined` \| `number` } \| { `index`: `number` ; `type`: `"CAPTURE_ZONE_PLANET_LEVEL_SCORE"` ; `value`: `undefined` \| `number` } \| { `type`: `"CAPTURE_ZONE_HOLD_BLOCKS_REQUIRED"` ; `value`: `undefined` \| `number` } \| { `type`: `"CAPTURE_ZONES_PER_5000_WORLD_RADIUS"` ; `value`: `undefined` \| `number` } | | `state` | [`LobbyConfigState`](Frontend_Panes_Lobbies_Reducer.md#lobbyconfigstate) | #### Returns { `currentValue`: `ExactArray10`<`number`\> ; `defaultValue`: `ExactArray10`<`number`\> ; `displayValue`: `undefined` = value; `warning`: `undefined` = undefined } \| { `currentValue`: `number` ; `defaultValue`: `number` ; `displayValue`: `undefined` = value; `warning`: `undefined` = undefined } \| { `currentValue`: `Tuple6`<`number`\> ; `defaultValue`: `Tuple6`<`number`\> ; `displayValue`: `undefined` = value; `warning`: `undefined` = undefined } \| { `currentValue`: `ExactArray10`<`number`\> ; `defaultValue`: `ExactArray10`<`number`\> ; `displayValue`: `number` = value; `warning`: `string` } \| { `currentValue`: `number` ; `defaultValue`: `number` ; `displayValue`: `number` = value; `warning`: `string` } \| { `currentValue`: `Tuple6`<`number`\> ; `defaultValue`: `Tuple6`<`number`\> ; `displayValue`: `number` = value; `warning`: `string` } \| { `currentValue`: `number` = value; `defaultValue`: `ExactArray10`<`number`\> ; `displayValue`: `number` = value; `warning`: `undefined` = undefined } \| { `currentValue`: `number` = value; `defaultValue`: `number` ; `displayValue`: `number` = value; `warning`: `undefined` = undefined } \| { `currentValue`: `number` = value; `defaultValue`: `Tuple6`<`number`\> ; `displayValue`: `number` = value; `warning`: `undefined` = undefined } --- ### ofPositiveInteger ▸ **ofPositiveInteger**(`__namedParameters`, `state`): { `currentValue`: `ExactArray10`<`number`\> ; `defaultValue`: `ExactArray10`<`number`\> ; `displayValue`: `undefined` = value; `warning`: `undefined` = undefined } \| { `currentValue`: `number` ; `defaultValue`: `number` ; `displayValue`: `undefined` = value; `warning`: `undefined` = undefined } \| { `currentValue`: `Tuple6`<`number`\> ; `defaultValue`: `Tuple6`<`number`\> ; `displayValue`: `undefined` = value; `warning`: `undefined` = undefined } \| { `currentValue`: `ExactArray10`<`number`\> ; `defaultValue`: `ExactArray10`<`number`\> ; `displayValue`: `number` = value; `warning`: `string` } \| { `currentValue`: `number` ; `defaultValue`: `number` ; `displayValue`: `number` = value; `warning`: `string` } \| { `currentValue`: `Tuple6`<`number`\> ; `defaultValue`: `Tuple6`<`number`\> ; `displayValue`: `number` = value; `warning`: `string` } \| { `currentValue`: `number` = value; `defaultValue`: `ExactArray10`<`number`\> ; `displayValue`: `number` = value; `warning`: `undefined` = undefined } \| { `currentValue`: `number` = value; `defaultValue`: `number` ; `displayValue`: `number` = value; `warning`: `undefined` = undefined } \| { `currentValue`: `number` = value; `defaultValue`: `Tuple6`<`number`\> ; `displayValue`: `number` = value; `warning`: `undefined` = undefined } #### Parameters | Name | Type | | :------------------ | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `__namedParameters` | { `type`: `"TOKEN_MINT_END_TIMESTAMP"` ; `value`: `undefined` \| `number` } \| { `type`: `"WORLD_RADIUS_MIN"` ; `value`: `undefined` \| `number` } \| { `type`: `"PLANETHASH_KEY"` ; `value`: `undefined` \| `number` } \| { `type`: `"SPACETYPE_KEY"` ; `value`: `undefined` \| `number` } \| { `type`: `"BIOMEBASE_KEY"` ; `value`: `undefined` \| `number` } \| { `type`: `"PERLIN_LENGTH_SCALE"` ; `value`: `undefined` \| `number` } \| { `type`: `"MAX_NATURAL_PLANET_LEVEL"` ; `value`: `undefined` \| `number` } \| { `type`: `"TIME_FACTOR_HUNDREDTHS"` ; `value`: `undefined` \| `number` } \| { `type`: `"PERLIN_THRESHOLD_1"` ; `value`: `undefined` \| `number` } \| { `type`: `"PERLIN_THRESHOLD_2"` ; `value`: `undefined` \| `number` } \| { `type`: `"PERLIN_THRESHOLD_3"` ; `value`: `undefined` \| `number` } \| { `type`: `"INIT_PERLIN_MIN"` ; `value`: `undefined` \| `number` } \| { `type`: `"INIT_PERLIN_MAX"` ; `value`: `undefined` \| `number` } \| { `type`: `"BIOME_THRESHOLD_1"` ; `value`: `undefined` \| `number` } \| { `type`: `"BIOME_THRESHOLD_2"` ; `value`: `undefined` \| `number` } \| { `index`: `number` ; `type`: `"PLANET_LEVEL_THRESHOLDS"` ; `value`: `undefined` \| `number` } \| { `type`: `"PLANET_RARITY"` ; `value`: `undefined` \| `number` } \| { `type`: `"PHOTOID_ACTIVATION_DELAY"` ; `value`: `undefined` \| `number` } \| { `type`: `"SPAWN_RIM_AREA"` ; `value`: `undefined` \| `number` } \| { `type`: `"LOCATION_REVEAL_COOLDOWN"` ; `value`: `undefined` \| `number` } \| { `type`: `"SILVER_SCORE_VALUE"` ; `value`: `undefined` \| `number` } \| { `index`: `number` ; `type`: `"ARTIFACT_POINT_VALUES"` ; `value`: `undefined` \| `number` } \| { `type`: `"SPACE_JUNK_LIMIT"` ; `value`: `undefined` \| `number` } \| { `index`: `number` ; `type`: `"PLANET_LEVEL_JUNK"` ; `value`: `undefined` \| `number` } \| { `type`: `"ABANDON_SPEED_CHANGE_PERCENT"` ; `value`: `undefined` \| `number` } \| { `type`: `"ABANDON_RANGE_CHANGE_PERCENT"` ; `value`: `undefined` \| `number` } \| { `type`: `"CAPTURE_ZONE_CHANGE_BLOCK_INTERVAL"` ; `value`: `undefined` \| `number` } \| { `type`: `"CAPTURE_ZONE_RADIUS"` ; `value`: `undefined` \| `number` } \| { `index`: `number` ; `type`: `"CAPTURE_ZONE_PLANET_LEVEL_SCORE"` ; `value`: `undefined` \| `number` } \| { `type`: `"CAPTURE_ZONE_HOLD_BLOCKS_REQUIRED"` ; `value`: `undefined` \| `number` } \| { `type`: `"CAPTURE_ZONES_PER_5000_WORLD_RADIUS"` ; `value`: `undefined` \| `number` } | | `state` | [`LobbyConfigState`](Frontend_Panes_Lobbies_Reducer.md#lobbyconfigstate) | #### Returns { `currentValue`: `ExactArray10`<`number`\> ; `defaultValue`: `ExactArray10`<`number`\> ; `displayValue`: `undefined` = value; `warning`: `undefined` = undefined } \| { `currentValue`: `number` ; `defaultValue`: `number` ; `displayValue`: `undefined` = value; `warning`: `undefined` = undefined } \| { `currentValue`: `Tuple6`<`number`\> ; `defaultValue`: `Tuple6`<`number`\> ; `displayValue`: `undefined` = value; `warning`: `undefined` = undefined } \| { `currentValue`: `ExactArray10`<`number`\> ; `defaultValue`: `ExactArray10`<`number`\> ; `displayValue`: `number` = value; `warning`: `string` } \| { `currentValue`: `number` ; `defaultValue`: `number` ; `displayValue`: `number` = value; `warning`: `string` } \| { `currentValue`: `Tuple6`<`number`\> ; `defaultValue`: `Tuple6`<`number`\> ; `displayValue`: `number` = value; `warning`: `string` } \| { `currentValue`: `number` = value; `defaultValue`: `ExactArray10`<`number`\> ; `displayValue`: `number` = value; `warning`: `undefined` = undefined } \| { `currentValue`: `number` = value; `defaultValue`: `number` ; `displayValue`: `number` = value; `warning`: `undefined` = undefined } \| { `currentValue`: `number` = value; `defaultValue`: `Tuple6`<`number`\> ; `displayValue`: `number` = value; `warning`: `undefined` = undefined } --- ### ofPositivePercent ▸ **ofPositivePercent**(`__namedParameters`, `state`): { `currentValue`: `ExactArray10`<`number`\> ; `defaultValue`: `ExactArray10`<`number`\> ; `displayValue`: `undefined` = value; `warning`: `undefined` = undefined } \| { `currentValue`: `number` ; `defaultValue`: `number` ; `displayValue`: `undefined` = value; `warning`: `undefined` = undefined } \| { `currentValue`: `Tuple6`<`number`\> ; `defaultValue`: `Tuple6`<`number`\> ; `displayValue`: `undefined` = value; `warning`: `undefined` = undefined } \| { `currentValue`: `ExactArray10`<`number`\> ; `defaultValue`: `ExactArray10`<`number`\> ; `displayValue`: `number` = value; `warning`: `string` } \| { `currentValue`: `number` ; `defaultValue`: `number` ; `displayValue`: `number` = value; `warning`: `string` } \| { `currentValue`: `Tuple6`<`number`\> ; `defaultValue`: `Tuple6`<`number`\> ; `displayValue`: `number` = value; `warning`: `string` } \| { `currentValue`: `number` ; `displayValue`: `number` = value; `warning`: `undefined` = undefined } #### Parameters | Name | Type | | :------------------ | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `__namedParameters` | { `type`: `"TOKEN_MINT_END_TIMESTAMP"` ; `value`: `undefined` \| `number` } \| { `type`: `"WORLD_RADIUS_MIN"` ; `value`: `undefined` \| `number` } \| { `type`: `"PLANETHASH_KEY"` ; `value`: `undefined` \| `number` } \| { `type`: `"SPACETYPE_KEY"` ; `value`: `undefined` \| `number` } \| { `type`: `"BIOMEBASE_KEY"` ; `value`: `undefined` \| `number` } \| { `type`: `"PERLIN_LENGTH_SCALE"` ; `value`: `undefined` \| `number` } \| { `type`: `"MAX_NATURAL_PLANET_LEVEL"` ; `value`: `undefined` \| `number` } \| { `type`: `"TIME_FACTOR_HUNDREDTHS"` ; `value`: `undefined` \| `number` } \| { `type`: `"PERLIN_THRESHOLD_1"` ; `value`: `undefined` \| `number` } \| { `type`: `"PERLIN_THRESHOLD_2"` ; `value`: `undefined` \| `number` } \| { `type`: `"PERLIN_THRESHOLD_3"` ; `value`: `undefined` \| `number` } \| { `type`: `"INIT_PERLIN_MIN"` ; `value`: `undefined` \| `number` } \| { `type`: `"INIT_PERLIN_MAX"` ; `value`: `undefined` \| `number` } \| { `type`: `"BIOME_THRESHOLD_1"` ; `value`: `undefined` \| `number` } \| { `type`: `"BIOME_THRESHOLD_2"` ; `value`: `undefined` \| `number` } \| { `index`: `number` ; `type`: `"PLANET_LEVEL_THRESHOLDS"` ; `value`: `undefined` \| `number` } \| { `type`: `"PLANET_RARITY"` ; `value`: `undefined` \| `number` } \| { `type`: `"PHOTOID_ACTIVATION_DELAY"` ; `value`: `undefined` \| `number` } \| { `type`: `"SPAWN_RIM_AREA"` ; `value`: `undefined` \| `number` } \| { `type`: `"LOCATION_REVEAL_COOLDOWN"` ; `value`: `undefined` \| `number` } \| { `type`: `"SILVER_SCORE_VALUE"` ; `value`: `undefined` \| `number` } \| { `index`: `number` ; `type`: `"ARTIFACT_POINT_VALUES"` ; `value`: `undefined` \| `number` } \| { `type`: `"SPACE_JUNK_LIMIT"` ; `value`: `undefined` \| `number` } \| { `index`: `number` ; `type`: `"PLANET_LEVEL_JUNK"` ; `value`: `undefined` \| `number` } \| { `type`: `"ABANDON_SPEED_CHANGE_PERCENT"` ; `value`: `undefined` \| `number` } \| { `type`: `"ABANDON_RANGE_CHANGE_PERCENT"` ; `value`: `undefined` \| `number` } \| { `type`: `"CAPTURE_ZONE_CHANGE_BLOCK_INTERVAL"` ; `value`: `undefined` \| `number` } \| { `type`: `"CAPTURE_ZONE_RADIUS"` ; `value`: `undefined` \| `number` } \| { `index`: `number` ; `type`: `"CAPTURE_ZONE_PLANET_LEVEL_SCORE"` ; `value`: `undefined` \| `number` } \| { `type`: `"CAPTURE_ZONE_HOLD_BLOCKS_REQUIRED"` ; `value`: `undefined` \| `number` } \| { `type`: `"CAPTURE_ZONES_PER_5000_WORLD_RADIUS"` ; `value`: `undefined` \| `number` } | | `state` | [`LobbyConfigState`](Frontend_Panes_Lobbies_Reducer.md#lobbyconfigstate) | #### Returns { `currentValue`: `ExactArray10`<`number`\> ; `defaultValue`: `ExactArray10`<`number`\> ; `displayValue`: `undefined` = value; `warning`: `undefined` = undefined } \| { `currentValue`: `number` ; `defaultValue`: `number` ; `displayValue`: `undefined` = value; `warning`: `undefined` = undefined } \| { `currentValue`: `Tuple6`<`number`\> ; `defaultValue`: `Tuple6`<`number`\> ; `displayValue`: `undefined` = value; `warning`: `undefined` = undefined } \| { `currentValue`: `ExactArray10`<`number`\> ; `defaultValue`: `ExactArray10`<`number`\> ; `displayValue`: `number` = value; `warning`: `string` } \| { `currentValue`: `number` ; `defaultValue`: `number` ; `displayValue`: `number` = value; `warning`: `string` } \| { `currentValue`: `Tuple6`<`number`\> ; `defaultValue`: `Tuple6`<`number`\> ; `displayValue`: `number` = value; `warning`: `string` } \| { `currentValue`: `number` ; `displayValue`: `number` = value; `warning`: `undefined` = undefined } --- ### ofSpaceJunkLimit ▸ **ofSpaceJunkLimit**(`__namedParameters`, `state`): { `currentValue`: `number` ; `defaultValue`: `number` ; `displayValue`: `undefined` = value; `warning`: `undefined` = undefined } \| { `currentValue`: `number` ; `defaultValue`: `number` ; `displayValue`: `number` = value; `warning`: `string` } \| { `currentValue`: `number` = value; `defaultValue`: `number` ; `displayValue`: `number` = value; `warning`: `undefined` = undefined } #### Parameters | Name | Type | | :------------------------ | :----------------------------------------------------------------------- | | `__namedParameters` | `Object` | | `__namedParameters.type` | `"SPACE_JUNK_LIMIT"` | | `__namedParameters.value` | `undefined` \| `number` | | `state` | [`LobbyConfigState`](Frontend_Panes_Lobbies_Reducer.md#lobbyconfigstate) | #### Returns { `currentValue`: `number` ; `defaultValue`: `number` ; `displayValue`: `undefined` = value; `warning`: `undefined` = undefined } \| { `currentValue`: `number` ; `defaultValue`: `number` ; `displayValue`: `number` = value; `warning`: `string` } \| { `currentValue`: `number` = value; `defaultValue`: `number` ; `displayValue`: `number` = value; `warning`: `undefined` = undefined } --- ### ofSpawnRimArea ▸ **ofSpawnRimArea**(`__namedParameters`, `state`): { `currentValue`: `number` ; `defaultValue`: `number` ; `displayValue`: `undefined` = value; `warning`: `undefined` = undefined } \| { `currentValue`: `number` = value; `defaultValue`: `number` ; `displayValue`: `number` = value; `warning`: `undefined` = undefined } \| { `currentValue`: `number` ; `defaultValue`: `number` ; `displayValue`: `number` = value; `warning`: `string` = 'Spawnable area must be larger' } #### Parameters | Name | Type | | :------------------------ | :----------------------------------------------------------------------- | | `__namedParameters` | `Object` | | `__namedParameters.type` | `"SPAWN_RIM_AREA"` | | `__namedParameters.value` | `undefined` \| `number` | | `state` | [`LobbyConfigState`](Frontend_Panes_Lobbies_Reducer.md#lobbyconfigstate) | #### Returns { `currentValue`: `number` ; `defaultValue`: `number` ; `displayValue`: `undefined` = value; `warning`: `undefined` = undefined } \| { `currentValue`: `number` = value; `defaultValue`: `number` ; `displayValue`: `number` = value; `warning`: `undefined` = undefined } \| { `currentValue`: `number` ; `defaultValue`: `number` ; `displayValue`: `number` = value; `warning`: `string` = 'Spawnable area must be larger' } --- ### ofTimeFactorHundredths ▸ **ofTimeFactorHundredths**(`__namedParameters`, `state`): { `currentValue`: `number` ; `defaultValue`: `number` ; `displayValue`: `undefined` = value; `warning`: `undefined` = undefined } \| { `currentValue`: `number` ; `defaultValue`: `number` ; `displayValue`: `number` = value; `warning`: `string` } \| { `currentValue`: `number` ; `defaultValue`: `number` ; `displayValue`: `number` = value; `warning`: `undefined` = undefined } #### Parameters | Name | Type | | :------------------------ | :----------------------------------------------------------------------- | | `__namedParameters` | `Object` | | `__namedParameters.type` | `"TIME_FACTOR_HUNDREDTHS"` | | `__namedParameters.value` | `undefined` \| `number` | | `state` | [`LobbyConfigState`](Frontend_Panes_Lobbies_Reducer.md#lobbyconfigstate) | #### Returns { `currentValue`: `number` ; `defaultValue`: `number` ; `displayValue`: `undefined` = value; `warning`: `undefined` = undefined } \| { `currentValue`: `number` ; `defaultValue`: `number` ; `displayValue`: `number` = value; `warning`: `string` } \| { `currentValue`: `number` ; `defaultValue`: `number` ; `displayValue`: `number` = value; `warning`: `undefined` = undefined } --- ### ofWorldRadiusMin ▸ **ofWorldRadiusMin**(`__namedParameters`, `state`): { `currentValue`: `number` ; `defaultValue`: `number` ; `displayValue`: `undefined` = value; `warning`: `undefined` = undefined } \| { `currentValue`: `number` ; `defaultValue`: `number` ; `displayValue`: `number` = value; `warning`: `string` } \| { `currentValue`: `number` = value; `defaultValue`: `number` ; `displayValue`: `number` = value; `warning`: `undefined` = undefined } #### Parameters | Name | Type | | :------------------------ | :----------------------------------------------------------------------- | | `__namedParameters` | `Object` | | `__namedParameters.type` | `"WORLD_RADIUS_MIN"` | | `__namedParameters.value` | `undefined` \| `number` | | `state` | [`LobbyConfigState`](Frontend_Panes_Lobbies_Reducer.md#lobbyconfigstate) | #### Returns { `currentValue`: `number` ; `defaultValue`: `number` ; `displayValue`: `undefined` = value; `warning`: `undefined` = undefined } \| { `currentValue`: `number` ; `defaultValue`: `number` ; `displayValue`: `number` = value; `warning`: `string` } \| { `currentValue`: `number` = value; `defaultValue`: `number` ; `displayValue`: `number` = value; `warning`: `undefined` = undefined } --- ### toInitializers ▸ **toInitializers**(`obj`): [`LobbyInitializers`](Frontend_Panes_Lobbies_Reducer.md#lobbyinitializers) #### Parameters | Name | Type | | :---- | :----------------------------------------------------------------------- | | `obj` | [`LobbyConfigState`](Frontend_Panes_Lobbies_Reducer.md#lobbyconfigstate) | #### Returns [`LobbyInitializers`](Frontend_Panes_Lobbies_Reducer.md#lobbyinitializers) ================================================ FILE: docs/modules/Frontend_Panes_Lobbies_SnarkPane.md ================================================ # Module: Frontend/Panes/Lobbies/SnarkPane ## Table of contents ### Functions - [SnarkPane](Frontend_Panes_Lobbies_SnarkPane.md#snarkpane) ## Functions ### SnarkPane ▸ **SnarkPane**(`__namedParameters`): `Element` #### Parameters | Name | Type | | :------------------ | :------------------------------------------------------------------------------------------ | | `__namedParameters` | [`LobbiesPaneProps`](../interfaces/Frontend_Panes_Lobbies_LobbiesUtils.LobbiesPaneProps.md) | #### Returns `Element` ================================================ FILE: docs/modules/Frontend_Panes_Lobbies_SpaceJunkPane.md ================================================ # Module: Frontend/Panes/Lobbies/SpaceJunkPane ## Table of contents ### Functions - [SpaceJunkPane](Frontend_Panes_Lobbies_SpaceJunkPane.md#spacejunkpane) ## Functions ### SpaceJunkPane ▸ **SpaceJunkPane**(`__namedParameters`): `Element` #### Parameters | Name | Type | | :------------------ | :------------------------------------------------------------------------------------------ | | `__namedParameters` | [`LobbiesPaneProps`](../interfaces/Frontend_Panes_Lobbies_LobbiesUtils.LobbiesPaneProps.md) | #### Returns `Element` ================================================ FILE: docs/modules/Frontend_Panes_Lobbies_SpaceTypeBiomePane.md ================================================ # Module: Frontend/Panes/Lobbies/SpaceTypeBiomePane ## Table of contents ### Functions - [SpaceTypeBiomePane](Frontend_Panes_Lobbies_SpaceTypeBiomePane.md#spacetypebiomepane) ## Functions ### SpaceTypeBiomePane ▸ **SpaceTypeBiomePane**(`__namedParameters`): `Element` #### Parameters | Name | Type | | :------------------ | :------------------------------------------------------------------------------------------ | | `__namedParameters` | [`LobbiesPaneProps`](../interfaces/Frontend_Panes_Lobbies_LobbiesUtils.LobbiesPaneProps.md) | #### Returns `Element` ================================================ FILE: docs/modules/Frontend_Panes_Lobbies_WorldSizePane.md ================================================ # Module: Frontend/Panes/Lobbies/WorldSizePane ## Table of contents ### Functions - [WorldSizePane](Frontend_Panes_Lobbies_WorldSizePane.md#worldsizepane) ## Functions ### WorldSizePane ▸ **WorldSizePane**(`__namedParameters`): `Element` #### Parameters | Name | Type | | :------------------ | :------------------------------------------------------------------------------------------ | | `__namedParameters` | [`LobbiesPaneProps`](../interfaces/Frontend_Panes_Lobbies_LobbiesUtils.LobbiesPaneProps.md) | #### Returns `Element` ================================================ FILE: docs/modules/Frontend_Panes_ManagePlanetArtifacts_ArtifactActions.md ================================================ # Module: Frontend/Panes/ManagePlanetArtifacts/ArtifactActions ## Table of contents ### Functions - [ArtifactActions](Frontend_Panes_ManagePlanetArtifacts_ArtifactActions.md#artifactactions) ## Functions ### ArtifactActions ▸ **ArtifactActions**(`__namedParameters`): `null` \| `Element` #### Parameters | Name | Type | | :----------------------------- | :----------- | | `__namedParameters` | `Object` | | `__namedParameters.artifactId` | `ArtifactId` | | `__namedParameters.depositOn?` | `LocationId` | #### Returns `null` \| `Element` ================================================ FILE: docs/modules/Frontend_Panes_ManagePlanetArtifacts_ManageArtifacts.md ================================================ # Module: Frontend/Panes/ManagePlanetArtifacts/ManageArtifacts ## Table of contents ### Functions - [ManageArtifactsPane](Frontend_Panes_ManagePlanetArtifacts_ManageArtifacts.md#manageartifactspane) ## Functions ### ManageArtifactsPane ▸ **ManageArtifactsPane**(`__namedParameters`): `Element` #### Parameters | Name | Type | | :------------------------------------ | :--------------------------------------------------------------------- | | `__namedParameters` | `Object` | | `__namedParameters.artifactsInWallet` | `Artifact`[] | | `__namedParameters.artifactsOnPlanet` | (`undefined` \| `Artifact`)[] | | `__namedParameters.modal` | [`ModalHandle`](../interfaces/Frontend_Views_ModalPane.ModalHandle.md) | | `__namedParameters.planet` | `LocatablePlanet` | | `__namedParameters.playerAddress` | `string` | #### Returns `Element` ================================================ FILE: docs/modules/Frontend_Panes_ManagePlanetArtifacts_ManagePlanetArtifactsPane.md ================================================ # Module: Frontend/Panes/ManagePlanetArtifacts/ManagePlanetArtifactsPane ## Table of contents ### Functions - [ManagePlanetArtifactsHelpContent](Frontend_Panes_ManagePlanetArtifacts_ManagePlanetArtifactsPane.md#manageplanetartifactshelpcontent) - [ManagePlanetArtifactsPane](Frontend_Panes_ManagePlanetArtifacts_ManagePlanetArtifactsPane.md#manageplanetartifactspane) - [PlanetInfoHelpContent](Frontend_Panes_ManagePlanetArtifacts_ManagePlanetArtifactsPane.md#planetinfohelpcontent) ## Functions ### ManagePlanetArtifactsHelpContent ▸ **ManagePlanetArtifactsHelpContent**(): `Element` #### Returns `Element` --- ### ManagePlanetArtifactsPane ▸ **ManagePlanetArtifactsPane**(`__namedParameters`): `Element` This is the place where a user can manage all of their artifacts on a particular planet. This includes prospecting, withdrawing, depositing, activating, and deactivating artifacts. #### Parameters | Name | Type | | :---------------------------------- | :--------------------------------------------------------------------- | | `__namedParameters` | `Object` | | `__namedParameters.initialPlanetId` | `undefined` \| `LocationId` | | `__namedParameters.modal` | [`ModalHandle`](../interfaces/Frontend_Views_ModalPane.ModalHandle.md) | #### Returns `Element` --- ### PlanetInfoHelpContent ▸ **PlanetInfoHelpContent**(): `Element` #### Returns `Element` ================================================ FILE: docs/modules/Frontend_Panes_ManagePlanetArtifacts_SortBy.md ================================================ # Module: Frontend/Panes/ManagePlanetArtifacts/SortBy ## Table of contents ### Functions - [SortBy](Frontend_Panes_ManagePlanetArtifacts_SortBy.md#sortby) ## Functions ### SortBy ▸ **SortBy**(`__namedParameters`): `Element` #### Parameters | Name | Type | | :---------------------------- | :---------------------------------------------- | | `__namedParameters` | `Object` | | `__namedParameters.sortBy` | `undefined` \| keyof `Upgrade` | | `__namedParameters.setSortBy` | (`k`: `undefined` \| keyof `Upgrade`) => `void` | #### Returns `Element` ================================================ FILE: docs/modules/Frontend_Panes_ManagePlanetArtifacts_UpgradeStatsView.md ================================================ # Module: Frontend/Panes/ManagePlanetArtifacts/UpgradeStatsView ## Table of contents ### Functions - [UpgradeStatsView](Frontend_Panes_ManagePlanetArtifacts_UpgradeStatsView.md#upgradestatsview) ## Functions ### UpgradeStatsView ▸ **UpgradeStatsView**(`__namedParameters`): `Element` #### Parameters | Name | Type | | :------------------------------- | :------------- | | `__namedParameters` | `Object` | | `__namedParameters.artifactType` | `ArtifactType` | | `__namedParameters.isActive` | `boolean` | | `__namedParameters.upgrade` | `Upgrade` | #### Returns `Element` ================================================ FILE: docs/modules/Frontend_Panes_OnboardingPane.md ================================================ # Module: Frontend/Panes/OnboardingPane ## Table of contents ### Functions - [default](Frontend_Panes_OnboardingPane.md#default) ## Functions ### default ▸ **default**(`__namedParameters`): `Element` #### Parameters | Name | Type | | :-------------------------- | :----------- | | `__namedParameters` | `Object` | | `__namedParameters.visible` | `boolean` | | `__namedParameters.onClose` | () => `void` | #### Returns `Element` ================================================ FILE: docs/modules/Frontend_Panes_PlanetContextPane.md ================================================ # Module: Frontend/Panes/PlanetContextPane ## Table of contents ### Functions - [PlanetContextPane](Frontend_Panes_PlanetContextPane.md#planetcontextpane) - [SelectedPlanetHelpContent](Frontend_Panes_PlanetContextPane.md#selectedplanethelpcontent) ## Functions ### PlanetContextPane ▸ **PlanetContextPane**(`__namedParameters`): `Element` #### Parameters | Name | Type | | :-------------------------- | :----------- | | `__namedParameters` | `Object` | | `__namedParameters.visible` | `boolean` | | `__namedParameters.onClose` | () => `void` | #### Returns `Element` --- ### SelectedPlanetHelpContent ▸ **SelectedPlanetHelpContent**(): `Element` #### Returns `Element` ================================================ FILE: docs/modules/Frontend_Panes_PlanetDexPane.md ================================================ # Module: Frontend/Panes/PlanetDexPane ## Table of contents ### Functions - [PlanetDexPane](Frontend_Panes_PlanetDexPane.md#planetdexpane) - [PlanetThumb](Frontend_Panes_PlanetDexPane.md#planetthumb) ## Functions ### PlanetDexPane ▸ **PlanetDexPane**(`__namedParameters`): `Element` #### Parameters | Name | Type | | :-------------------------- | :----------- | | `__namedParameters` | `Object` | | `__namedParameters.visible` | `boolean` | | `__namedParameters.onClose` | () => `void` | #### Returns `Element` --- ### PlanetThumb ▸ **PlanetThumb**(`__namedParameters`): `Element` #### Parameters | Name | Type | | :------------------------- | :------- | | `__namedParameters` | `Object` | | `__namedParameters.planet` | `Planet` | #### Returns `Element` ================================================ FILE: docs/modules/Frontend_Panes_PlanetInfoPane.md ================================================ # Module: Frontend/Panes/PlanetInfoPane ## Table of contents ### Functions - [PlanetInfoPane](Frontend_Panes_PlanetInfoPane.md#planetinfopane) ## Functions ### PlanetInfoPane ▸ **PlanetInfoPane**(`__namedParameters`): `Element` This pane contains misc info about the planet, which does not have a place in the main Planet Context Pane. #### Parameters | Name | Type | | :---------------------------------- | :-------------------------- | | `__namedParameters` | `Object` | | `__namedParameters.initialPlanetId` | `undefined` \| `LocationId` | #### Returns `Element` ================================================ FILE: docs/modules/Frontend_Panes_PlayerArtifactsPane.md ================================================ # Module: Frontend/Panes/PlayerArtifactsPane ## Table of contents ### Functions - [PlayerArtifactsPane](Frontend_Panes_PlayerArtifactsPane.md#playerartifactspane) ## Functions ### PlayerArtifactsPane ▸ **PlayerArtifactsPane**(`__namedParameters`): `Element` #### Parameters | Name | Type | | :-------------------------- | :----------- | | `__namedParameters` | `Object` | | `__namedParameters.visible` | `boolean` | | `__namedParameters.onClose` | () => `void` | #### Returns `Element` ================================================ FILE: docs/modules/Frontend_Panes_PluginEditorPane.md ================================================ # Module: Frontend/Panes/PluginEditorPane ## Table of contents ### Functions - [PluginEditorPane](Frontend_Panes_PluginEditorPane.md#plugineditorpane) ## Functions ### PluginEditorPane ▸ **PluginEditorPane**(`__namedParameters`): `Element` Component for editing plugins. Saving causes its containing modal to be closed, and the `overwrite` to be called, indicating that the given plugin's source should be overwritten and reloaded. If no plugin id is provided, assumes we're editing a new plugin. #### Parameters | Name | Type | | :------------------------------ | :---------------------------------------------------------------------------------------- | | `__namedParameters` | `Object` | | `__namedParameters.pluginHost?` | `null` \| [`PluginManager`](../classes/Backend_GameLogic_PluginManager.PluginManager.md) | | `__namedParameters.pluginId?` | `PluginId` | | `__namedParameters.overwrite` | (`newPluginName`: `string`, `newPluginCode`: `string`, `pluginId?`: `PluginId`) => `void` | | `__namedParameters.setIsOpen` | (`open`: `boolean`) => `void` | #### Returns `Element` ================================================ FILE: docs/modules/Frontend_Panes_PluginLibraryPane.md ================================================ # Module: Frontend/Panes/PluginLibraryPane ## Table of contents ### Functions - [PluginLibraryPane](Frontend_Panes_PluginLibraryPane.md#pluginlibrarypane) ## Functions ### PluginLibraryPane ▸ **PluginLibraryPane**(`__namedParameters`): `Element` This modal presents an overview of all of the player's plugins. Has a button to add a new plugin, and lists out all the existing plugins, allowing the user to view their titles, as well as either edit, delete, or open their modal. You can think of this as the plugin process list, the Activity Monitor of Dark forest. #### Parameters | Name | Type | | :---------------------------------- | :----------------------------------------------------------------- | | `__namedParameters` | `Object` | | `__namedParameters.gameUIManager` | [`default`](../classes/Backend_GameLogic_GameUIManager.default.md) | | `__namedParameters.modalsContainer` | `Element` | | `__namedParameters.visible` | `boolean` | | `__namedParameters.onClose` | () => `void` | #### Returns `Element` ================================================ FILE: docs/modules/Frontend_Panes_PrivatePane.md ================================================ # Module: Frontend/Panes/PrivatePane ## Table of contents ### Functions - [PrivatePane](Frontend_Panes_PrivatePane.md#privatepane) ## Functions ### PrivatePane ▸ **PrivatePane**(`__namedParameters`): `Element` #### Parameters | Name | Type | | :-------------------------- | :----------- | | `__namedParameters` | `Object` | | `__namedParameters.visible` | `boolean` | | `__namedParameters.onClose` | () => `void` | #### Returns `Element` ================================================ FILE: docs/modules/Frontend_Panes_SettingsPane.md ================================================ # Module: Frontend/Panes/SettingsPane ## Table of contents ### Functions - [SettingsPane](Frontend_Panes_SettingsPane.md#settingspane) ## Functions ### SettingsPane ▸ **SettingsPane**(`__namedParameters`): `Element` #### Parameters | Name | Type | | :-------------------------------- | :-------------- | | `__namedParameters` | `Object` | | `__namedParameters.ethConnection` | `EthConnection` | | `__namedParameters.visible` | `boolean` | | `__namedParameters.onClose` | () => `void` | | `__namedParameters.onOpenPrivate` | () => `void` | #### Returns `Element` ================================================ FILE: docs/modules/Frontend_Panes_Tooltip.md ================================================ # Module: Frontend/Panes/Tooltip ## Table of contents ### Interfaces - [TooltipProps](../interfaces/Frontend_Panes_Tooltip.TooltipProps.md) - [TooltipTriggerProps](../interfaces/Frontend_Panes_Tooltip.TooltipTriggerProps.md) ### Functions - [Tooltip](Frontend_Panes_Tooltip.md#tooltip) - [TooltipTrigger](Frontend_Panes_Tooltip.md#tooltiptrigger) ## Functions ### Tooltip ▸ **Tooltip**(`props`): `null` \| `Element` At any given moment, there can only be one tooltip visible in the game. This is true because a player only has one mouse cursor on the screen, and therefore can only be hovering over a single [TooltipTrigger](Frontend_Panes_Tooltip.md#tooltiptrigger) element at any given time. This component is responsible for keeping track of which tooltip has been hovered over, and rendering the corresponding content. #### Parameters | Name | Type | | :------ | :--------------------------------------------------------------------- | | `props` | [`TooltipProps`](../interfaces/Frontend_Panes_Tooltip.TooltipProps.md) | #### Returns `null` \| `Element` --- ### TooltipTrigger ▸ **TooltipTrigger**(`props`): `Element` When the player hovers over this element, triggers the tooltip with the given name to be displayed on top of everything. #### Parameters | Name | Type | | :------ | :----------------------------------------------------------------------------------- | | `props` | [`TooltipTriggerProps`](../interfaces/Frontend_Panes_Tooltip.TooltipTriggerProps.md) | #### Returns `Element` ================================================ FILE: docs/modules/Frontend_Panes_TooltipPanes.md ================================================ # Module: Frontend/Panes/TooltipPanes ## Table of contents ### Functions - [ActivateArtifactPane](Frontend_Panes_TooltipPanes.md#activateartifactpane) - [BonusDefenseTooltipPane](Frontend_Panes_TooltipPanes.md#bonusdefensetooltippane) - [BonusEnergyCapTooltipPane](Frontend_Panes_TooltipPanes.md#bonusenergycaptooltippane) - [BonusEnergyGroTooltipPane](Frontend_Panes_TooltipPanes.md#bonusenergygrotooltippane) - [BonusRangeTooltipPane](Frontend_Panes_TooltipPanes.md#bonusrangetooltippane) - [BonusSpaceJunkTooltipPane](Frontend_Panes_TooltipPanes.md#bonusspacejunktooltippane) - [BonusSpeedTooltipPane](Frontend_Panes_TooltipPanes.md#bonusspeedtooltippane) - [BonusTooltipPane](Frontend_Panes_TooltipPanes.md#bonustooltippane) - [ClowntownTooltipPane](Frontend_Panes_TooltipPanes.md#clowntowntooltippane) - [CurrentMiningTooltipPane](Frontend_Panes_TooltipPanes.md#currentminingtooltippane) - [DeactivateArtifactPane](Frontend_Panes_TooltipPanes.md#deactivateartifactpane) - [DefenseMultiplierPane](Frontend_Panes_TooltipPanes.md#defensemultiplierpane) - [DepositArtifactPane](Frontend_Panes_TooltipPanes.md#depositartifactpane) - [EnergyCapMultiplierPane](Frontend_Panes_TooltipPanes.md#energycapmultiplierpane) - [EnergyGrowthMultiplierPane](Frontend_Panes_TooltipPanes.md#energygrowthmultiplierpane) - [EnergyGrowthTooltipPane](Frontend_Panes_TooltipPanes.md#energygrowthtooltippane) - [EnergyTooltipPane](Frontend_Panes_TooltipPanes.md#energytooltippane) - [MaxLevelTooltipPane](Frontend_Panes_TooltipPanes.md#maxleveltooltippane) - [MinEnergyTooltipPane](Frontend_Panes_TooltipPanes.md#minenergytooltippane) - [MiningPauseTooltipPane](Frontend_Panes_TooltipPanes.md#miningpausetooltippane) - [MiningTargetTooltipPane](Frontend_Panes_TooltipPanes.md#miningtargettooltippane) - [ModalBroadcastTooltipPane](Frontend_Panes_TooltipPanes.md#modalbroadcasttooltippane) - [ModalHelpTooltipPane](Frontend_Panes_TooltipPanes.md#modalhelptooltippane) - [ModalLeaderboardTooltipPane](Frontend_Panes_TooltipPanes.md#modalleaderboardtooltippane) - [ModalPlanetDetailsTooltipPane](Frontend_Panes_TooltipPanes.md#modalplanetdetailstooltippane) - [ModalPlanetDexTooltipPane](Frontend_Panes_TooltipPanes.md#modalplanetdextooltippane) - [ModalTwitterVerificationTooltipPane](Frontend_Panes_TooltipPanes.md#modaltwitterverificationtooltippane) - [ModalUpgradeDetailsTooltipPane](Frontend_Panes_TooltipPanes.md#modalupgradedetailstooltippane) - [NetworkHealthPane](Frontend_Panes_TooltipPanes.md#networkhealthpane) - [PiratesTooltipPane](Frontend_Panes_TooltipPanes.md#piratestooltippane) - [PlanetRankTooltipPane](Frontend_Panes_TooltipPanes.md#planetranktooltippane) - [RangeMultiplierPane](Frontend_Panes_TooltipPanes.md#rangemultiplierpane) - [RangeTooltipPane](Frontend_Panes_TooltipPanes.md#rangetooltippane) - [RankTooltipPane](Frontend_Panes_TooltipPanes.md#ranktooltippane) - [ScoreTooltipPane](Frontend_Panes_TooltipPanes.md#scoretooltippane) - [SelectedSilverTooltipPane](Frontend_Panes_TooltipPanes.md#selectedsilvertooltippane) - [SilverCapTooltipPane](Frontend_Panes_TooltipPanes.md#silvercaptooltippane) - [SilverGrowthTooltipPane](Frontend_Panes_TooltipPanes.md#silvergrowthtooltippane) - [SilverProdTooltipPane](Frontend_Panes_TooltipPanes.md#silverprodtooltippane) - [SilverTooltipPane](Frontend_Panes_TooltipPanes.md#silvertooltippane) - [SpeedMultiplierPane](Frontend_Panes_TooltipPanes.md#speedmultiplierpane) - [Time50TooltipPane](Frontend_Panes_TooltipPanes.md#time50tooltippane) - [Time90TooltipPane](Frontend_Panes_TooltipPanes.md#time90tooltippane) - [TimeUntilActivationPossiblePane](Frontend_Panes_TooltipPanes.md#timeuntilactivationpossiblepane) - [TooltipContent](Frontend_Panes_TooltipPanes.md#tooltipcontent) - [TwitterHandleTooltipPane](Frontend_Panes_TooltipPanes.md#twitterhandletooltippane) - [UpgradesTooltipPane](Frontend_Panes_TooltipPanes.md#upgradestooltippane) - [WithdrawArtifactPane](Frontend_Panes_TooltipPanes.md#withdrawartifactpane) - [WithdrawSilverButton](Frontend_Panes_TooltipPanes.md#withdrawsilverbutton) ## Functions ### ActivateArtifactPane ▸ **ActivateArtifactPane**(): `Element` #### Returns `Element` --- ### BonusDefenseTooltipPane ▸ **BonusDefenseTooltipPane**(): `Element` #### Returns `Element` --- ### BonusEnergyCapTooltipPane ▸ **BonusEnergyCapTooltipPane**(): `Element` #### Returns `Element` --- ### BonusEnergyGroTooltipPane ▸ **BonusEnergyGroTooltipPane**(): `Element` #### Returns `Element` --- ### BonusRangeTooltipPane ▸ **BonusRangeTooltipPane**(): `Element` #### Returns `Element` --- ### BonusSpaceJunkTooltipPane ▸ **BonusSpaceJunkTooltipPane**(): `Element` #### Returns `Element` --- ### BonusSpeedTooltipPane ▸ **BonusSpeedTooltipPane**(): `Element` #### Returns `Element` --- ### BonusTooltipPane ▸ **BonusTooltipPane**(): `Element` #### Returns `Element` --- ### ClowntownTooltipPane ▸ **ClowntownTooltipPane**(): `Element` #### Returns `Element` --- ### CurrentMiningTooltipPane ▸ **CurrentMiningTooltipPane**(): `Element` #### Returns `Element` --- ### DeactivateArtifactPane ▸ **DeactivateArtifactPane**(): `Element` #### Returns `Element` --- ### DefenseMultiplierPane ▸ **DefenseMultiplierPane**(): `Element` #### Returns `Element` --- ### DepositArtifactPane ▸ **DepositArtifactPane**(): `Element` #### Returns `Element` --- ### EnergyCapMultiplierPane ▸ **EnergyCapMultiplierPane**(): `Element` #### Returns `Element` --- ### EnergyGrowthMultiplierPane ▸ **EnergyGrowthMultiplierPane**(): `Element` #### Returns `Element` --- ### EnergyGrowthTooltipPane ▸ **EnergyGrowthTooltipPane**(): `Element` #### Returns `Element` --- ### EnergyTooltipPane ▸ **EnergyTooltipPane**(): `Element` #### Returns `Element` --- ### MaxLevelTooltipPane ▸ **MaxLevelTooltipPane**(): `Element` #### Returns `Element` --- ### MinEnergyTooltipPane ▸ **MinEnergyTooltipPane**(): `Element` #### Returns `Element` --- ### MiningPauseTooltipPane ▸ **MiningPauseTooltipPane**(): `Element` #### Returns `Element` --- ### MiningTargetTooltipPane ▸ **MiningTargetTooltipPane**(): `Element` #### Returns `Element` --- ### ModalBroadcastTooltipPane ▸ **ModalBroadcastTooltipPane**(): `Element` #### Returns `Element` --- ### ModalHelpTooltipPane ▸ **ModalHelpTooltipPane**(): `Element` #### Returns `Element` --- ### ModalLeaderboardTooltipPane ▸ **ModalLeaderboardTooltipPane**(): `Element` #### Returns `Element` --- ### ModalPlanetDetailsTooltipPane ▸ **ModalPlanetDetailsTooltipPane**(): `Element` #### Returns `Element` --- ### ModalPlanetDexTooltipPane ▸ **ModalPlanetDexTooltipPane**(): `Element` #### Returns `Element` --- ### ModalTwitterVerificationTooltipPane ▸ **ModalTwitterVerificationTooltipPane**(): `Element` #### Returns `Element` --- ### ModalUpgradeDetailsTooltipPane ▸ **ModalUpgradeDetailsTooltipPane**(): `Element` #### Returns `Element` --- ### NetworkHealthPane ▸ **NetworkHealthPane**(): `Element` #### Returns `Element` --- ### PiratesTooltipPane ▸ **PiratesTooltipPane**(): `Element` #### Returns `Element` --- ### PlanetRankTooltipPane ▸ **PlanetRankTooltipPane**(): `Element` #### Returns `Element` --- ### RangeMultiplierPane ▸ **RangeMultiplierPane**(): `Element` #### Returns `Element` --- ### RangeTooltipPane ▸ **RangeTooltipPane**(): `Element` #### Returns `Element` --- ### RankTooltipPane ▸ **RankTooltipPane**(): `Element` #### Returns `Element` --- ### ScoreTooltipPane ▸ **ScoreTooltipPane**(): `Element` #### Returns `Element` --- ### SelectedSilverTooltipPane ▸ **SelectedSilverTooltipPane**(): `Element` #### Returns `Element` --- ### SilverCapTooltipPane ▸ **SilverCapTooltipPane**(): `Element` #### Returns `Element` --- ### SilverGrowthTooltipPane ▸ **SilverGrowthTooltipPane**(): `Element` #### Returns `Element` --- ### SilverProdTooltipPane ▸ **SilverProdTooltipPane**(): `Element` #### Returns `Element` --- ### SilverTooltipPane ▸ **SilverTooltipPane**(): `Element` #### Returns `Element` --- ### SpeedMultiplierPane ▸ **SpeedMultiplierPane**(): `Element` #### Returns `Element` --- ### Time50TooltipPane ▸ **Time50TooltipPane**(): `Element` #### Returns `Element` --- ### Time90TooltipPane ▸ **Time90TooltipPane**(): `Element` #### Returns `Element` --- ### TimeUntilActivationPossiblePane ▸ **TimeUntilActivationPossiblePane**(): `Element` #### Returns `Element` --- ### TooltipContent ▸ **TooltipContent**(`__namedParameters`): `Element` #### Parameters | Name | Type | | :----------------------- | :--------------------------- | | `__namedParameters` | `Object` | | `__namedParameters.name` | `undefined` \| `TooltipName` | #### Returns `Element` --- ### TwitterHandleTooltipPane ▸ **TwitterHandleTooltipPane**(): `Element` #### Returns `Element` --- ### UpgradesTooltipPane ▸ **UpgradesTooltipPane**(): `Element` #### Returns `Element` --- ### WithdrawArtifactPane ▸ **WithdrawArtifactPane**(): `Element` #### Returns `Element` --- ### WithdrawSilverButton ▸ **WithdrawSilverButton**(): `Element` #### Returns `Element` ================================================ FILE: docs/modules/Frontend_Panes_TransactionLogPane.md ================================================ # Module: Frontend/Panes/TransactionLogPane ## Table of contents ### Functions - [TransactionLogPane](Frontend_Panes_TransactionLogPane.md#transactionlogpane) ## Functions ### TransactionLogPane ▸ **TransactionLogPane**(`__namedParameters`): `Element` #### Parameters | Name | Type | | :-------------------------- | :----------- | | `__namedParameters` | `Object` | | `__namedParameters.visible` | `boolean` | | `__namedParameters.onClose` | () => `void` | #### Returns `Element` ================================================ FILE: docs/modules/Frontend_Panes_TutorialPane.md ================================================ # Module: Frontend/Panes/TutorialPane ## Table of contents ### Functions - [TutorialPane](Frontend_Panes_TutorialPane.md#tutorialpane) ## Functions ### TutorialPane ▸ **TutorialPane**(`__namedParameters`): `Element` #### Parameters | Name | Type | | :------------------------------- | :----------------------------------------------------- | | `__namedParameters` | `Object` | | `__namedParameters.tutorialHook` | [`Hook`](types_global_GlobalTypes.md#hook)<`boolean`\> | #### Returns `Element` ================================================ FILE: docs/modules/Frontend_Panes_TwitterVerifyPane.md ================================================ # Module: Frontend/Panes/TwitterVerifyPane ## Table of contents ### Functions - [TwitterVerifyPane](Frontend_Panes_TwitterVerifyPane.md#twitterverifypane) ## Functions ### TwitterVerifyPane ▸ **TwitterVerifyPane**(`__namedParameters`): `Element` #### Parameters | Name | Type | | :-------------------------- | :----------- | | `__namedParameters` | `Object` | | `__namedParameters.visible` | `boolean` | | `__namedParameters.onClose` | () => `void` | #### Returns `Element` ================================================ FILE: docs/modules/Frontend_Panes_UpgradeDetailsPane.md ================================================ # Module: Frontend/Panes/UpgradeDetailsPane ## Table of contents ### Functions - [UpgradeDetailsPane](Frontend_Panes_UpgradeDetailsPane.md#upgradedetailspane) - [UpgradeDetailsPaneHelpContent](Frontend_Panes_UpgradeDetailsPane.md#upgradedetailspanehelpcontent) ## Functions ### UpgradeDetailsPane ▸ **UpgradeDetailsPane**(`__namedParameters`): `Element` #### Parameters | Name | Type | | :---------------------------------- | :--------------------------------------------------------------------- | | `__namedParameters` | `Object` | | `__namedParameters.initialPlanetId` | `undefined` \| `LocationId` | | `__namedParameters.modal` | [`ModalHandle`](../interfaces/Frontend_Views_ModalPane.ModalHandle.md) | #### Returns `Element` --- ### UpgradeDetailsPaneHelpContent ▸ **UpgradeDetailsPaneHelpContent**(): `Element` #### Returns `Element` ================================================ FILE: docs/modules/Frontend_Panes_WikiPane.md ================================================ # Module: Frontend/Panes/WikiPane ## Table of contents ### Functions - [WikiPane](Frontend_Panes_WikiPane.md#wikipane) ## Functions ### WikiPane ▸ **WikiPane**(`__namedParameters`): `Element` #### Parameters | Name | Type | | :--------------------------- | :---------- | | `__namedParameters` | `Object` | | `__namedParameters.children` | `ReactNode` | #### Returns `Element` ================================================ FILE: docs/modules/Frontend_Panes_ZoomPane.md ================================================ # Module: Frontend/Panes/ZoomPane ## Table of contents ### Functions - [ZoomPane](Frontend_Panes_ZoomPane.md#zoompane) ## Functions ### ZoomPane ▸ **ZoomPane**(): `Element` #### Returns `Element` ================================================ FILE: docs/modules/Frontend_Renderers_Artifacts_ArtifactRenderer.md ================================================ # Module: Frontend/Renderers/Artifacts/ArtifactRenderer ## Table of contents ### Classes - [ArtifactRenderer](../classes/Frontend_Renderers_Artifacts_ArtifactRenderer.ArtifactRenderer.md) ### Variables - [aDexCanvasH](Frontend_Renderers_Artifacts_ArtifactRenderer.md#adexcanvash) - [aDexCanvasW](Frontend_Renderers_Artifacts_ArtifactRenderer.md#adexcanvasw) - [aListCanvasH](Frontend_Renderers_Artifacts_ArtifactRenderer.md#alistcanvash) - [aListCanvasW](Frontend_Renderers_Artifacts_ArtifactRenderer.md#alistcanvasw) - [artifactColM](Frontend_Renderers_Artifacts_ArtifactRenderer.md#artifactcolm) - [artifactColW](Frontend_Renderers_Artifacts_ArtifactRenderer.md#artifactcolw) ## Variables ### aDexCanvasH • `Const` **aDexCanvasH**: `number` --- ### aDexCanvasW • `Const` **aDexCanvasW**: `number` --- ### aListCanvasH • `Const` **aListCanvasH**: `400` --- ### aListCanvasW • `Const` **aListCanvasW**: `48` --- ### artifactColM • `Const` **artifactColM**: `32` --- ### artifactColW • `Const` **artifactColW**: `number` ================================================ FILE: docs/modules/Frontend_Renderers_GifRenderer.md ================================================ # Module: Frontend/Renderers/GifRenderer ## Table of contents ### Classes - [GifRenderer](../classes/Frontend_Renderers_GifRenderer.GifRenderer.md) ================================================ FILE: docs/modules/Frontend_Renderers_LandingPageCanvas.md ================================================ # Module: Frontend/Renderers/LandingPageCanvas ## Table of contents ### Functions - [LandingPageBackground](Frontend_Renderers_LandingPageCanvas.md#landingpagebackground) - [default](Frontend_Renderers_LandingPageCanvas.md#default) ## Functions ### LandingPageBackground ▸ **LandingPageBackground**(): `Element` #### Returns `Element` --- ### default ▸ **default**(): `Element` #### Returns `Element` ================================================ FILE: docs/modules/Frontend_Renderers_PlanetscapeRenderer_PlanetIcons.md ================================================ # Module: Frontend/Renderers/PlanetscapeRenderer/PlanetIcons ## Table of contents ### Functions - [PlanetIcons](Frontend_Renderers_PlanetscapeRenderer_PlanetIcons.md#planeticons) ## Functions ### PlanetIcons ▸ **PlanetIcons**(`__namedParameters`): `Element` #### Parameters | Name | Type | | :------------------------- | :---------------------- | | `__namedParameters` | `Object` | | `__namedParameters.planet` | `undefined` \| `Planet` | #### Returns `Element` ================================================ FILE: docs/modules/Frontend_Styles_Colors.md ================================================ # Module: Frontend/Styles/Colors ## Table of contents ### Variables - [ANCIENT_BLUE](Frontend_Styles_Colors.md#ancient_blue) - [ANCIENT_PURPLE](Frontend_Styles_Colors.md#ancient_purple) - [BiomeBackgroundColors](Frontend_Styles_Colors.md#biomebackgroundcolors) - [BiomeTextColors](Frontend_Styles_Colors.md#biometextcolors) - [RarityColors](Frontend_Styles_Colors.md#raritycolors) ## Variables ### ANCIENT_BLUE • `Const` **ANCIENT_BLUE**: `"#b2fffc"` --- ### ANCIENT_PURPLE • `Const` **ANCIENT_PURPLE**: `"#d23191"` --- ### BiomeBackgroundColors • `Const` **BiomeBackgroundColors**: `Object` --- ### BiomeTextColors • `Const` **BiomeTextColors**: `Object` --- ### RarityColors • `Const` **RarityColors**: `Object` ================================================ FILE: docs/modules/Frontend_Styles_Mixins.md ================================================ # Module: Frontend/Styles/Mixins ## Table of contents ### Functions - [planetBackground](Frontend_Styles_Mixins.md#planetbackground) ## Functions ### planetBackground ▸ **planetBackground**(`__namedParameters`): `""` \| `FlattenSimpleInterpolation` #### Parameters | Name | Type | | :------------------------- | :---------------------- | | `__namedParameters` | `Object` | | `__namedParameters.planet` | `undefined` \| `Planet` | #### Returns `""` \| `FlattenSimpleInterpolation` ================================================ FILE: docs/modules/Frontend_Styles_dfstyles.md ================================================ # Module: Frontend/Styles/dfstyles ## Table of contents ### Variables - [ARTIFACT_ROW_H](Frontend_Styles_dfstyles.md#artifact_row_h) - [SPACE_TYPE_COLORS](Frontend_Styles_dfstyles.md#space_type_colors) - [default](Frontend_Styles_dfstyles.md#default) - [snips](Frontend_Styles_dfstyles.md#snips) ## Variables ### ARTIFACT_ROW_H • `Const` **ARTIFACT_ROW_H**: `48` --- ### SPACE_TYPE_COLORS • `Const` **SPACE_TYPE_COLORS**: `Object` --- ### default • `Const` **default**: `Object` #### Type declaration | Name | Type | | :----------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `borderRadius` | `string` | | `colors` | { `artifactBackground`: `string` = 'rgb(21, 17, 71)'; `background`: `string` ; `backgrounddark`: `string` ; `backgroundlight`: `string` ; `backgroundlighter`: `string` ; `blueBackground`: `string` ; `border`: `string` ; `borderDark`: `string` ; `borderDarker`: `string` ; `borderDarkest`: `string` ; `dfblue`: `string` ; `dfgreen`: `string` ; `dfgreendark`: `string` ; `dfgreenlight`: `string` ; `dforange`: `string` ; `dfpurple`: `string` ; `dfred`: `string` ; `dfwhite`: `string` ; `dfyellow`: `string` ; `icons`: { `blog`: `string` = '#ffcb1f'; `discord`: `string` = '#7289da'; `email`: `string` = '#D44638'; `github`: `string` = '#8e65db'; `twitter`: `string` = '#1DA1F2' } ; `subbertext`: `string` ; `subbesttext`: `string` ; `subtext`: `string` ; `text`: `string` ; `textLight`: `string` } | | `colors.artifactBackground` | `string` | | `colors.background` | `string` | | `colors.backgrounddark` | `string` | | `colors.backgroundlight` | `string` | | `colors.backgroundlighter` | `string` | | `colors.blueBackground` | `string` | | `colors.border` | `string` | | `colors.borderDark` | `string` | | `colors.borderDarker` | `string` | | `colors.borderDarkest` | `string` | | `colors.dfblue` | `string` | | `colors.dfgreen` | `string` | | `colors.dfgreendark` | `string` | | `colors.dfgreenlight` | `string` | | `colors.dforange` | `string` | | `colors.dfpurple` | `string` | | `colors.dfred` | `string` | | `colors.dfwhite` | `string` | | `colors.dfyellow` | `string` | | `colors.icons` | { `blog`: `string` = '#ffcb1f'; `discord`: `string` = '#7289da'; `email`: `string` = '#D44638'; `github`: `string` = '#8e65db'; `twitter`: `string` = '#1DA1F2' } | | `colors.icons.blog` | `string` | | `colors.icons.discord` | `string` | | `colors.icons.email` | `string` | | `colors.icons.github` | `string` | | `colors.icons.twitter` | `string` | | `colors.subbertext` | `string` | | `colors.subbesttext` | `string` | | `colors.subtext` | `string` | | `colors.text` | `string` | | `colors.textLight` | `string` | | `fontH1` | `string` | | `fontH1S` | `string` | | `fontH2` | `string` | | `fontSize` | `string` | | `fontSizeS` | `string` | | `fontSizeXS` | `string` | | `game` | { `bonuscolors`: { `def`: `string` = 'hsl(231, 73%, 70%)'; `energyCap`: `string` = 'hsl(360, 73%, 70%)'; `energyGro`: `string` = 'hsl(136, 73%, 70%)'; `range`: `string` = 'hsl(50, 73%, 70%)'; `spaceJunk`: `string` = 'hsl(43, 33%, 29%)'; `speed`: `string` = 'hsl(290, 73%, 70%)' } ; `canvasbg`: `string` = '#100544'; `fontSize`: `string` = '12pt'; `rangecolors`: { `color100`: `string` = '#050228'; `color25`: `string` = '#050238'; `color50`: `string` = '#050233'; `colorenergy`: `string` = '#080330'; `dash`: `string` = '#9691bf'; `dashenergy`: `string` = '#f5c082' } ; `styles`: { `active`: `string` = 'filter: brightness(80%)'; `animProps`: `string` = 'ease-in-out infinite alternate-reverse' } ; `terminalFontSize`: `string` = '10pt'; `terminalWidth`: `string` = '240pt'; `toolbarHeight`: `string` = '12em' } | | `game.bonuscolors` | { `def`: `string` = 'hsl(231, 73%, 70%)'; `energyCap`: `string` = 'hsl(360, 73%, 70%)'; `energyGro`: `string` = 'hsl(136, 73%, 70%)'; `range`: `string` = 'hsl(50, 73%, 70%)'; `spaceJunk`: `string` = 'hsl(43, 33%, 29%)'; `speed`: `string` = 'hsl(290, 73%, 70%)' } | | `game.bonuscolors.def` | `string` | | `game.bonuscolors.energyCap` | `string` | | `game.bonuscolors.energyGro` | `string` | | `game.bonuscolors.range` | `string` | | `game.bonuscolors.spaceJunk` | `string` | | `game.bonuscolors.speed` | `string` | | `game.canvasbg` | `string` | | `game.fontSize` | `string` | | `game.rangecolors` | { `color100`: `string` = '#050228'; `color25`: `string` = '#050238'; `color50`: `string` = '#050233'; `colorenergy`: `string` = '#080330'; `dash`: `string` = '#9691bf'; `dashenergy`: `string` = '#f5c082' } | | `game.rangecolors.color100` | `string` | | `game.rangecolors.color25` | `string` | | `game.rangecolors.color50` | `string` | | `game.rangecolors.colorenergy` | `string` | | `game.rangecolors.dash` | `string` | | `game.rangecolors.dashenergy` | `string` | | `game.styles` | { `active`: `string` = 'filter: brightness(80%)'; `animProps`: `string` = 'ease-in-out infinite alternate-reverse' } | | `game.styles.active` | `string` | | `game.styles.animProps` | `string` | | `game.terminalFontSize` | `string` | | `game.terminalWidth` | `string` | | `game.toolbarHeight` | `string` | | `prefabs` | { `noselect`: `FlattenSimpleInterpolation` } | | `prefabs.noselect` | `FlattenSimpleInterpolation` | | `screenSizeS` | `string` | | `titleFont` | `string` | --- ### snips • `Const` **snips**: `Object` #### Type declaration | Name | Type | | :----------------------- | :-------------------------------------- | | `absoluteTopLeft` | `FlattenSimpleInterpolation` | | `bigPadding` | `FlattenSimpleInterpolation` | | `defaultBackground` | `string` | | `defaultModalWidth` | `FlattenSimpleInterpolation` | | `destroyedBackground` | `CSSStyleDeclaration` & `CSSProperties` | | `pane` | `string` | | `roundedBorders` | `string` | | `roundedBordersWithEdge` | `FlattenSimpleInterpolation` | ================================================ FILE: docs/modules/Frontend_Utils_AppHooks.md ================================================ # Module: Frontend/Utils/AppHooks ## Table of contents ### Type aliases - [TransactionRecord](Frontend_Utils_AppHooks.md#transactionrecord) ### Variables - [TopLevelDivProvider](Frontend_Utils_AppHooks.md#topleveldivprovider) - [UIManagerProvider](Frontend_Utils_AppHooks.md#uimanagerprovider) ### Functions - [useAccount](Frontend_Utils_AppHooks.md#useaccount) - [useActiveArtifact](Frontend_Utils_AppHooks.md#useactiveartifact) - [useArtifact](Frontend_Utils_AppHooks.md#useartifact) - [useHoverArtifact](Frontend_Utils_AppHooks.md#usehoverartifact) - [useHoverArtifactId](Frontend_Utils_AppHooks.md#usehoverartifactid) - [useHoverPlanet](Frontend_Utils_AppHooks.md#usehoverplanet) - [useLeaderboard](Frontend_Utils_AppHooks.md#useleaderboard) - [useMyArtifactsList](Frontend_Utils_AppHooks.md#usemyartifactslist) - [useOverlayContainer](Frontend_Utils_AppHooks.md#useoverlaycontainer) - [usePaused](Frontend_Utils_AppHooks.md#usepaused) - [usePlanet](Frontend_Utils_AppHooks.md#useplanet) - [usePlanetArtifacts](Frontend_Utils_AppHooks.md#useplanetartifacts) - [usePlanetInactiveArtifacts](Frontend_Utils_AppHooks.md#useplanetinactiveartifacts) - [usePlayer](Frontend_Utils_AppHooks.md#useplayer) - [usePopAllOnSelectedPlanetChanged](Frontend_Utils_AppHooks.md#usepopallonselectedplanetchanged) - [useSelectedArtifact](Frontend_Utils_AppHooks.md#useselectedartifact) - [useSelectedPlanet](Frontend_Utils_AppHooks.md#useselectedplanet) - [useSelectedPlanetId](Frontend_Utils_AppHooks.md#useselectedplanetid) - [useTopLevelDiv](Frontend_Utils_AppHooks.md#usetopleveldiv) - [useTransactionLog](Frontend_Utils_AppHooks.md#usetransactionlog) - [useUIManager](Frontend_Utils_AppHooks.md#useuimanager) ## Type aliases ### TransactionRecord Ƭ **TransactionRecord**: `Record`<`TransactionId`, `Transaction`\> ## Variables ### TopLevelDivProvider • **TopLevelDivProvider**: `Provider`<`HTMLDivElement`\> --- ### UIManagerProvider • **UIManagerProvider**: `Provider`<[`default`](../classes/Backend_GameLogic_GameUIManager.default.md)\> ## Functions ### useAccount ▸ **useAccount**(`uiManager`): `EthAddress` \| `undefined` Get the currently used account on the client. #### Parameters | Name | Type | Description | | :---------- | :----------------------------------------------------------------- | :------------------------ | | `uiManager` | [`default`](../classes/Backend_GameLogic_GameUIManager.default.md) | instance of GameUIManager | #### Returns `EthAddress` \| `undefined` --- ### useActiveArtifact ▸ **useActiveArtifact**(`planet`, `uiManager`): `Artifact` \| `undefined` #### Parameters | Name | Type | | :---------- | :--------------------------------------------------------------------------------- | | `planet` | [`Wrapper`](../classes/Backend_Utils_Wrapper.Wrapper.md)<`undefined` \| `Planet`\> | | `uiManager` | [`default`](../classes/Backend_GameLogic_GameUIManager.default.md) | #### Returns `Artifact` \| `undefined` --- ### useArtifact ▸ **useArtifact**(`uiManager`, `artifactId`): [`Wrapper`](../classes/Backend_Utils_Wrapper.Wrapper.md)<`undefined` \| `Artifact`\> #### Parameters | Name | Type | | :----------- | :----------------------------------------------------------------- | | `uiManager` | [`default`](../classes/Backend_GameLogic_GameUIManager.default.md) | | `artifactId` | `ArtifactId` | #### Returns [`Wrapper`](../classes/Backend_Utils_Wrapper.Wrapper.md)<`undefined` \| `Artifact`\> --- ### useHoverArtifact ▸ **useHoverArtifact**(`uiManager`): [`Wrapper`](../classes/Backend_Utils_Wrapper.Wrapper.md)<`Artifact` \| `undefined`\> #### Parameters | Name | Type | | :---------- | :----------------------------------------------------------------- | | `uiManager` | [`default`](../classes/Backend_GameLogic_GameUIManager.default.md) | #### Returns [`Wrapper`](../classes/Backend_Utils_Wrapper.Wrapper.md)<`Artifact` \| `undefined`\> --- ### useHoverArtifactId ▸ **useHoverArtifactId**(`uiManager`): [`Wrapper`](../classes/Backend_Utils_Wrapper.Wrapper.md)<`ArtifactId` \| `undefined`\> #### Parameters | Name | Type | | :---------- | :----------------------------------------------------------------- | | `uiManager` | [`default`](../classes/Backend_GameLogic_GameUIManager.default.md) | #### Returns [`Wrapper`](../classes/Backend_Utils_Wrapper.Wrapper.md)<`ArtifactId` \| `undefined`\> --- ### useHoverPlanet ▸ **useHoverPlanet**(`uiManager`): [`Wrapper`](../classes/Backend_Utils_Wrapper.Wrapper.md)<`Planet` \| `undefined`\> Create a subscription to the currently hovering planet. #### Parameters | Name | Type | Description | | :---------- | :----------------------------------------------------------------- | :------------------------ | | `uiManager` | [`default`](../classes/Backend_GameLogic_GameUIManager.default.md) | instance of GameUIManager | #### Returns [`Wrapper`](../classes/Backend_Utils_Wrapper.Wrapper.md)<`Planet` \| `undefined`\> --- ### useLeaderboard ▸ **useLeaderboard**(`poll?`): `Object` Loads the leaderboard #### Parameters | Name | Type | Default value | | :----- | :---------------------- | :------------ | | `poll` | `undefined` \| `number` | `undefined` | #### Returns `Object` | Name | Type | | :------------ | :--------------------------- | | `error` | `Error` \| `undefined` | | `leaderboard` | `Leaderboard` \| `undefined` | --- ### useMyArtifactsList ▸ **useMyArtifactsList**(`uiManager`): `Artifact`[] #### Parameters | Name | Type | | :---------- | :----------------------------------------------------------------- | | `uiManager` | [`default`](../classes/Backend_GameLogic_GameUIManager.default.md) | #### Returns `Artifact`[] --- ### useOverlayContainer ▸ **useOverlayContainer**(): `HTMLDivElement` \| `null` #### Returns `HTMLDivElement` \| `null` --- ### usePaused ▸ **usePaused**(): `boolean` #### Returns `boolean` --- ### usePlanet ▸ **usePlanet**(`uiManager`, `locationId`): [`Wrapper`](../classes/Backend_Utils_Wrapper.Wrapper.md)<`Planet` \| `undefined`\> #### Parameters | Name | Type | | :----------- | :----------------------------------------------------------------- | | `uiManager` | [`default`](../classes/Backend_GameLogic_GameUIManager.default.md) | | `locationId` | `undefined` \| `LocationId` | #### Returns [`Wrapper`](../classes/Backend_Utils_Wrapper.Wrapper.md)<`Planet` \| `undefined`\> --- ### usePlanetArtifacts ▸ **usePlanetArtifacts**(`planet`, `uiManager`): `Artifact`[] #### Parameters | Name | Type | | :---------- | :--------------------------------------------------------------------------------- | | `planet` | [`Wrapper`](../classes/Backend_Utils_Wrapper.Wrapper.md)<`undefined` \| `Planet`\> | | `uiManager` | [`default`](../classes/Backend_GameLogic_GameUIManager.default.md) | #### Returns `Artifact`[] --- ### usePlanetInactiveArtifacts ▸ **usePlanetInactiveArtifacts**(`planet`, `uiManager`): `Artifact`[] #### Parameters | Name | Type | | :---------- | :--------------------------------------------------------------------------------- | | `planet` | [`Wrapper`](../classes/Backend_Utils_Wrapper.Wrapper.md)<`undefined` \| `Planet`\> | | `uiManager` | [`default`](../classes/Backend_GameLogic_GameUIManager.default.md) | #### Returns `Artifact`[] --- ### usePlayer ▸ **usePlayer**(`uiManager`, `ethAddress?`): [`Wrapper`](../classes/Backend_Utils_Wrapper.Wrapper.md)<`Player` \| `undefined`\> Hook which gets you the player, and updates whenever that player's twitter or score changes. #### Parameters | Name | Type | | :------------ | :----------------------------------------------------------------- | | `uiManager` | [`default`](../classes/Backend_GameLogic_GameUIManager.default.md) | | `ethAddress?` | `EthAddress` | #### Returns [`Wrapper`](../classes/Backend_Utils_Wrapper.Wrapper.md)<`Player` \| `undefined`\> --- ### usePopAllOnSelectedPlanetChanged ▸ **usePopAllOnSelectedPlanetChanged**(`modal`, `startingId`): `void` #### Parameters | Name | Type | | :----------- | :--------------------------------------------------------------------- | | `modal` | [`ModalHandle`](../interfaces/Frontend_Views_ModalPane.ModalHandle.md) | | `startingId` | `undefined` \| `LocationId` | #### Returns `void` --- ### useSelectedArtifact ▸ **useSelectedArtifact**(`uiManager`): [`Wrapper`](../classes/Backend_Utils_Wrapper.Wrapper.md)<`Artifact` \| `undefined`\> Create a subscription to the currently selected artifact. #### Parameters | Name | Type | Description | | :---------- | :----------------------------------------------------------------- | :------------------------ | | `uiManager` | [`default`](../classes/Backend_GameLogic_GameUIManager.default.md) | instance of GameUIManager | #### Returns [`Wrapper`](../classes/Backend_Utils_Wrapper.Wrapper.md)<`Artifact` \| `undefined`\> --- ### useSelectedPlanet ▸ **useSelectedPlanet**(`uiManager`): [`Wrapper`](../classes/Backend_Utils_Wrapper.Wrapper.md)<`Planet` \| `undefined`\> Create a subscription to the currently selected planet. #### Parameters | Name | Type | Description | | :---------- | :----------------------------------------------------------------- | :------------------------ | | `uiManager` | [`default`](../classes/Backend_GameLogic_GameUIManager.default.md) | instance of GameUIManager | #### Returns [`Wrapper`](../classes/Backend_Utils_Wrapper.Wrapper.md)<`Planet` \| `undefined`\> --- ### useSelectedPlanetId ▸ **useSelectedPlanetId**(`uiManager`, `defaultId?`): [`Wrapper`](../classes/Backend_Utils_Wrapper.Wrapper.md)<`undefined` \| `LocationId`\> #### Parameters | Name | Type | | :----------- | :----------------------------------------------------------------- | | `uiManager` | [`default`](../classes/Backend_GameLogic_GameUIManager.default.md) | | `defaultId?` | `LocationId` | #### Returns [`Wrapper`](../classes/Backend_Utils_Wrapper.Wrapper.md)<`undefined` \| `LocationId`\> --- ### useTopLevelDiv ▸ **useTopLevelDiv**(): `HTMLDivElement` #### Returns `HTMLDivElement` --- ### useTransactionLog ▸ **useTransactionLog**(): [`Wrapper`](../classes/Backend_Utils_Wrapper.Wrapper.md)<[`TransactionRecord`](Frontend_Utils_AppHooks.md#transactionrecord)\> Creates subscriptions to all contract transaction events to keep an up to date list of all transactions and their states. #### Returns [`Wrapper`](../classes/Backend_Utils_Wrapper.Wrapper.md)<[`TransactionRecord`](Frontend_Utils_AppHooks.md#transactionrecord)\> --- ### useUIManager ▸ **useUIManager**(): [`default`](../classes/Backend_GameLogic_GameUIManager.default.md) #### Returns [`default`](../classes/Backend_GameLogic_GameUIManager.default.md) ================================================ FILE: docs/modules/Frontend_Utils_BrowserChecks.md ================================================ # Module: Frontend/Utils/BrowserChecks ## Table of contents ### Enumerations - [Incompatibility](../enums/Frontend_Utils_BrowserChecks.Incompatibility.md) ### Functions - [hasTouchscreen](Frontend_Utils_BrowserChecks.md#hastouchscreen) - [isBrave](Frontend_Utils_BrowserChecks.md#isbrave) - [isChrome](Frontend_Utils_BrowserChecks.md#ischrome) - [isFirefox](Frontend_Utils_BrowserChecks.md#isfirefox) - [isMobileOrTablet](Frontend_Utils_BrowserChecks.md#ismobileortablet) - [unsupportedFeatures](Frontend_Utils_BrowserChecks.md#unsupportedfeatures) ## Functions ### hasTouchscreen ▸ **hasTouchscreen**(): `boolean` #### Returns `boolean` --- ### isBrave ▸ **isBrave**(): `Promise`<`boolean`\> #### Returns `Promise`<`boolean`\> --- ### isChrome ▸ **isChrome**(): `boolean` #### Returns `boolean` --- ### isFirefox ▸ **isFirefox**(): `boolean` #### Returns `boolean` --- ### isMobileOrTablet ▸ **isMobileOrTablet**(): `boolean` #### Returns `boolean` --- ### unsupportedFeatures ▸ **unsupportedFeatures**(): `Promise`<[`Incompatibility`](../enums/Frontend_Utils_BrowserChecks.Incompatibility.md)[]\> #### Returns `Promise`<[`Incompatibility`](../enums/Frontend_Utils_BrowserChecks.Incompatibility.md)[]\> ================================================ FILE: docs/modules/Frontend_Utils_EmitterHooks.md ================================================ # Module: Frontend/Utils/EmitterHooks ## Table of contents ### Functions - [useEmitterSubscribe](Frontend_Utils_EmitterHooks.md#useemittersubscribe) - [useEmitterValue](Frontend_Utils_EmitterHooks.md#useemittervalue) - [useWrappedEmitter](Frontend_Utils_EmitterHooks.md#usewrappedemitter) ## Functions ### useEmitterSubscribe ▸ **useEmitterSubscribe**<`T`\>(`emitter`, `callback`, `deps`): `void` Execute something on emitter callback #### Type parameters | Name | | :--- | | `T` | #### Parameters | Name | Type | Description | | :--------- | :----------------- | :--------------------------- | | `emitter` | `Monomitter`<`T`\> | `Monomitter` to subscribe to | | `callback` | `Callback`<`T`\> | callback to subscribe | | `deps` | `DependencyList` | - | #### Returns `void` --- ### useEmitterValue ▸ **useEmitterValue**<`T`\>(`emitter`, `initialVal`): `T` Use returned value from an emitter #### Type parameters | Name | | :--- | | `T` | #### Parameters | Name | Type | Description | | :----------- | :----------------- | :--------------------------- | | `emitter` | `Monomitter`<`T`\> | `Monomitter` to subscribe to | | `initialVal` | `T` | initial state value | #### Returns `T` --- ### useWrappedEmitter ▸ **useWrappedEmitter**<`T`\>(`emitter`, `initialVal`): [`Wrapper`](../classes/Backend_Utils_Wrapper.Wrapper.md)<`T` \| `undefined`\> Use returned value from an emitter, and clone the reference - used to force an update to the UI #### Type parameters | Name | | :--- | | `T` | #### Parameters | Name | Type | Description | | :----------- | :-------------------------------- | :--------------------------- | | `emitter` | `Monomitter`<`undefined` \| `T`\> | `Monomitter` to subscribe to | | `initialVal` | `undefined` \| `T` | initial state value | #### Returns [`Wrapper`](../classes/Backend_Utils_Wrapper.Wrapper.md)<`T` \| `undefined`\> ================================================ FILE: docs/modules/Frontend_Utils_EmitterUtils.md ================================================ # Module: Frontend/Utils/EmitterUtils ## Table of contents ### Interfaces - [Diff](../interfaces/Frontend_Utils_EmitterUtils.Diff.md) ### Functions - [generateDiffEmitter](Frontend_Utils_EmitterUtils.md#generatediffemitter) - [getArtifactId](Frontend_Utils_EmitterUtils.md#getartifactid) - [getArtifactOwner](Frontend_Utils_EmitterUtils.md#getartifactowner) - [getDisposableEmitter](Frontend_Utils_EmitterUtils.md#getdisposableemitter) - [getObjectWithIdFromMap](Frontend_Utils_EmitterUtils.md#getobjectwithidfrommap) - [getPlanetId](Frontend_Utils_EmitterUtils.md#getplanetid) - [getPlanetOwner](Frontend_Utils_EmitterUtils.md#getplanetowner) - [setObjectSyncState](Frontend_Utils_EmitterUtils.md#setobjectsyncstate) ## Functions ### generateDiffEmitter ▸ **generateDiffEmitter**<`Obj`\>(`emitter`): `Monomitter`<[`Diff`](../interfaces/Frontend_Utils_EmitterUtils.Diff.md)<`Obj`\> \| `undefined`\> Wraps an existing emitter and emits an event with the current and previous values #### Type parameters | Name | | :---- | | `Obj` | #### Parameters | Name | Type | Description | | :-------- | :---------------------------------- | :--------------------------------- | | `emitter` | `Monomitter`<`undefined` \| `Obj`\> | an emitter announcing game objects | #### Returns `Monomitter`<[`Diff`](../interfaces/Frontend_Utils_EmitterUtils.Diff.md)<`Obj`\> \| `undefined`\> --- ### getArtifactId ▸ **getArtifactId**(`a`): `ArtifactId` #### Parameters | Name | Type | | :--- | :--------- | | `a` | `Artifact` | #### Returns `ArtifactId` --- ### getArtifactOwner ▸ **getArtifactOwner**(`a`): `EthAddress` #### Parameters | Name | Type | | :--- | :--------- | | `a` | `Artifact` | #### Returns `EthAddress` --- ### getDisposableEmitter ▸ **getDisposableEmitter**<`Obj`, `Id`\>(`objMap`, `objId`, `objUpdated$`): `Monomitter`<`Obj` \| `undefined`\> Create a monomitter to emit objects with a given id from a cached map of ids to objects. Not intended for re-use #### Type parameters | Name | | :---- | | `Obj` | | `Id` | #### Parameters | Name | Type | Description | | :------------ | :------------------ | :------------------------------------------------------ | | `objMap` | `Map`<`Id`, `Obj`\> | the cached map of `` | | `objId` | `Id` | the object id to select | | `objUpdated$` | `Monomitter`<`Id`\> | emitter which indicates when an object has been updated | #### Returns `Monomitter`<`Obj` \| `undefined`\> --- ### getObjectWithIdFromMap ▸ **getObjectWithIdFromMap**<`Obj`, `Id`\>(`objMap`, `objId$`, `objUpdated$`): `Monomitter`<`Obj` \| `undefined`\> Create a monomitter to emit objects with a given id from a cached map of ids to objects. #### Type parameters | Name | | :---- | | `Obj` | | `Id` | #### Parameters | Name | Type | Description | | :------------ | :--------------------------------- | :------------------------------------------------------ | | `objMap` | `Map`<`Id`, `Obj`\> | the cached map of `` | | `objId$` | `Monomitter`<`undefined` \| `Id`\> | the object id to select | | `objUpdated$` | `Monomitter`<`Id`\> | emitter which indicates when an object has been updated | #### Returns `Monomitter`<`Obj` \| `undefined`\> --- ### getPlanetId ▸ **getPlanetId**(`p`): `LocationId` #### Parameters | Name | Type | | :--- | :------- | | `p` | `Planet` | #### Returns `LocationId` --- ### getPlanetOwner ▸ **getPlanetOwner**(`p`): `EthAddress` #### Parameters | Name | Type | | :--- | :------- | | `p` | `Planet` | #### Returns `EthAddress` --- ### setObjectSyncState ▸ **setObjectSyncState**<`Obj`, `Id`\>(`objectMap`, `myObjectMap`, `address`, `objUpdated$`, `myObjListUpdated$`, `getId`, `getOwner`, `obj`): `void` Utility function for setting a game entity into our internal data stores in a way that is friendly to our application. Caches the object into a map, syncs it to a map of our owned objects, and also emits a message that the object was updated. #### Type parameters | Name | | :---- | | `Obj` | | `Id` | #### Parameters | Name | Type | Description | | :------------------ | :--------------------------------- | :---------------------------------------------- | | `objectMap` | `Map`<`Id`, `Obj`\> | map that caches known objects | | `myObjectMap` | `Map`<`Id`, `Obj`\> | map that caches known objects owned by the user | | `address` | `undefined` \| `EthAddress` | the user's account address | | `objUpdated$` | `Monomitter`<`Id`\> | emitter for announcing object updates | | `myObjListUpdated$` | `Monomitter`<`Map`<`Id`, `Obj`\>\> | - | | `getId` | (`o`: `Obj`) => `Id` | - | | `getOwner` | (`o`: `Obj`) => `EthAddress` | - | | `obj` | `Obj` | the object we want to cache | #### Returns `void` ================================================ FILE: docs/modules/Frontend_Utils_Hooks.md ================================================ # Module: Frontend/Utils/Hooks ## Table of contents ### Functions - [usePoll](Frontend_Utils_Hooks.md#usepoll) ## Functions ### usePoll ▸ **usePoll**(`cb`, `poll?`, `execFirst?`): `void` Executes the callback `cb` every `poll` ms #### Parameters | Name | Type | Default value | Description | | :---------- | :----------------------- | :------------ | :------------------------------------------------- | | `cb` | () => `void` | `undefined` | callback to execute | | `poll` | `undefined` \| `number` | `undefined` | ms to poll | | `execFirst` | `undefined` \| `boolean` | `undefined` | if we want to execute the callback on first render | #### Returns `void` ================================================ FILE: docs/modules/Frontend_Utils_KeyEmitters.md ================================================ # Module: Frontend/Utils/KeyEmitters ## Table of contents ### Variables - [keyDown$](Frontend_Utils_KeyEmitters.md#keydown$) - [keyUp$](Frontend_Utils_KeyEmitters.md#keyup$) ### Functions - [listenForKeyboardEvents](Frontend_Utils_KeyEmitters.md#listenforkeyboardevents) - [unlinkKeyboardEvents](Frontend_Utils_KeyEmitters.md#unlinkkeyboardevents) - [useIsDown](Frontend_Utils_KeyEmitters.md#useisdown) - [useOnUp](Frontend_Utils_KeyEmitters.md#useonup) ## Variables ### keyDown$ • `Const` **keyDown$**: `Monomitter`<[`Wrapper`](../classes/Backend_Utils_Wrapper.Wrapper.md)<`string`\>\> --- ### keyUp$ • `Const` **keyUp$**: `Monomitter`<[`Wrapper`](../classes/Backend_Utils_Wrapper.Wrapper.md)<`string`\>\> ## Functions ### listenForKeyboardEvents ▸ **listenForKeyboardEvents**(): `void` #### Returns `void` --- ### unlinkKeyboardEvents ▸ **unlinkKeyboardEvents**(): `void` #### Returns `void` --- ### useIsDown ▸ **useIsDown**(`key?`): `boolean` #### Parameters | Name | Type | | :----- | :------- | | `key?` | `string` | #### Returns `boolean` --- ### useOnUp ▸ **useOnUp**(`key`, `onUp`, `deps?`): `void` #### Parameters | Name | Type | Default value | | :----- | :--------------- | :------------ | | `key` | `string` | `undefined` | | `onUp` | () => `void` | `undefined` | | `deps` | `DependencyList` | `[]` | #### Returns `void` ================================================ FILE: docs/modules/Frontend_Utils_SettingsHooks.md ================================================ # Module: Frontend/Utils/SettingsHooks ## Table of contents ### Variables - [ALL_AUTO_GAS_SETTINGS](Frontend_Utils_SettingsHooks.md#all_auto_gas_settings) - [settingChanged$](Frontend_Utils_SettingsHooks.md#settingchanged$) ### Functions - [BooleanSetting](Frontend_Utils_SettingsHooks.md#booleansetting) - [ColorSetting](Frontend_Utils_SettingsHooks.md#colorsetting) - [MultiSelectSetting](Frontend_Utils_SettingsHooks.md#multiselectsetting) - [NumberSetting](Frontend_Utils_SettingsHooks.md#numbersetting) - [StringSetting](Frontend_Utils_SettingsHooks.md#stringsetting) - [getBooleanSetting](Frontend_Utils_SettingsHooks.md#getbooleansetting) - [getLocalStorageSettingKey](Frontend_Utils_SettingsHooks.md#getlocalstoragesettingkey) - [getNumberSetting](Frontend_Utils_SettingsHooks.md#getnumbersetting) - [getSetting](Frontend_Utils_SettingsHooks.md#getsetting) - [pollSetting](Frontend_Utils_SettingsHooks.md#pollsetting) - [setBooleanSetting](Frontend_Utils_SettingsHooks.md#setbooleansetting) - [setNumberSetting](Frontend_Utils_SettingsHooks.md#setnumbersetting) - [setSetting](Frontend_Utils_SettingsHooks.md#setsetting) - [useBooleanSetting](Frontend_Utils_SettingsHooks.md#usebooleansetting) - [useNumberSetting](Frontend_Utils_SettingsHooks.md#usenumbersetting) - [useSetting](Frontend_Utils_SettingsHooks.md#usesetting) ## Variables ### ALL_AUTO_GAS_SETTINGS • `Const` **ALL_AUTO_GAS_SETTINGS**: `AutoGasSetting`[] --- ### settingChanged$ • `Const` **settingChanged$**: `Monomitter`<`Setting`\> Whenever a setting changes, we publish the setting's name to this event emitter. ## Functions ### BooleanSetting ▸ **BooleanSetting**(`__namedParameters`): `Element` React component that renders a checkbox representing the current value of this particular setting, interpreting its value as a boolean. Allows the player to click on the checkbox to toggle the setting. Toggling the setting both notifies the rest of the game that the given setting was changed, and also saves it to local storage. #### Parameters | Name | Type | | :-------------------------------------- | :----------------------------------------------------------------- | | `__namedParameters` | `Object` | | `__namedParameters.setting` | `Setting` | | `__namedParameters.settingDescription?` | `string` | | `__namedParameters.uiManager` | [`default`](../classes/Backend_GameLogic_GameUIManager.default.md) | #### Returns `Element` --- ### ColorSetting ▸ **ColorSetting**(`__namedParameters`): `Element` #### Parameters | Name | Type | | :-------------------------------------- | :----------------------------------------------------------------- | | `__namedParameters` | `Object` | | `__namedParameters.setting` | `Setting` | | `__namedParameters.settingDescription?` | `string` | | `__namedParameters.uiManager` | [`default`](../classes/Backend_GameLogic_GameUIManager.default.md) | #### Returns `Element` --- ### MultiSelectSetting ▸ **MultiSelectSetting**(`__namedParameters`): `Element` UI that is kept in-sync with a particular setting which allows you to set that setting to one of several options. #### Parameters | Name | Type | | :---------------------------- | :----------------------------------------------------------------- | | `__namedParameters` | `Object` | | `__namedParameters.labels` | `string`[] | | `__namedParameters.setting` | `Setting` | | `__namedParameters.style?` | `CSSProperties` | | `__namedParameters.uiManager` | [`default`](../classes/Backend_GameLogic_GameUIManager.default.md) | | `__namedParameters.values` | `string`[] | | `__namedParameters.wide?` | `boolean` | #### Returns `Element` --- ### NumberSetting ▸ **NumberSetting**(`__namedParameters`): `Element` #### Parameters | Name | Type | | :---------------------------- | :----------------------------------------------------------------- | | `__namedParameters` | `Object` | | `__namedParameters.setting` | `Setting` | | `__namedParameters.uiManager` | [`default`](../classes/Backend_GameLogic_GameUIManager.default.md) | #### Returns `Element` --- ### StringSetting ▸ **StringSetting**(`__namedParameters`): `Element` #### Parameters | Name | Type | | :-------------------------------------- | :----------------------------------------------------------------- | | `__namedParameters` | `Object` | | `__namedParameters.setting` | `Setting` | | `__namedParameters.settingDescription?` | `string` | | `__namedParameters.uiManager` | [`default`](../classes/Backend_GameLogic_GameUIManager.default.md) | #### Returns `Element` --- ### getBooleanSetting ▸ **getBooleanSetting**(`config`, `setting`): `boolean` Loads from local storage, and interprets as a boolean the setting with the given name. #### Parameters | Name | Type | | :-------- | :--------------------- | | `config` | `SettingStorageConfig` | | `setting` | `Setting` | #### Returns `boolean` --- ### getLocalStorageSettingKey ▸ **getLocalStorageSettingKey**(`__namedParameters`, `setting`): `string` Each setting is stored in local storage. Each account has their own setting. #### Parameters | Name | Type | | :------------------ | :--------------------- | | `__namedParameters` | `SettingStorageConfig` | | `setting` | `Setting` | #### Returns `string` --- ### getNumberSetting ▸ **getNumberSetting**(`config`, `setting`): `number` Loads from local storage, and interprets as a boolean the setting with the given name. #### Parameters | Name | Type | | :-------- | :--------------------- | | `config` | `SettingStorageConfig` | | `setting` | `Setting` | #### Returns `number` --- ### getSetting ▸ **getSetting**(`config`, `setting`): `string` Read the local storage setting from local storage. #### Parameters | Name | Type | | :-------- | :--------------------- | | `config` | `SettingStorageConfig` | | `setting` | `Setting` | #### Returns `string` --- ### pollSetting ▸ **pollSetting**(`config`, `setting`): `ReturnType` Some settings can be set from another browser window. In particular, the 'auto accept transaction' setting is set from multiple browser windows. As a result, the local storage setting can get out of sync with the in memory setting. To fix this, we can poll the given setting from local storage, and notify the rest of the game that it changed if it changed. #### Parameters | Name | Type | | :-------- | :--------------------- | | `config` | `SettingStorageConfig` | | `setting` | `Setting` | #### Returns `ReturnType` --- ### setBooleanSetting ▸ **setBooleanSetting**(`config`, `setting`, `value`): `void` Save the given setting to local storage. Publish an event to [settingChanged$](Frontend_Utils_SettingsHooks.md#settingchanged$). #### Parameters | Name | Type | | :-------- | :--------------------- | | `config` | `SettingStorageConfig` | | `setting` | `Setting` | | `value` | `boolean` | #### Returns `void` --- ### setNumberSetting ▸ **setNumberSetting**(`config`, `setting`, `value`): `void` Save the given setting to local storage. Publish an event to [settingChanged$](Frontend_Utils_SettingsHooks.md#settingchanged$). #### Parameters | Name | Type | | :-------- | :--------------------- | | `config` | `SettingStorageConfig` | | `setting` | `Setting` | | `value` | `number` | #### Returns `void` --- ### setSetting ▸ **setSetting**(`__namedParameters`, `setting`, `value`): `void` Save the given setting to local storage. Publish an event to [settingChanged$](Frontend_Utils_SettingsHooks.md#settingchanged$). #### Parameters | Name | Type | | :------------------ | :--------------------- | | `__namedParameters` | `SettingStorageConfig` | | `setting` | `Setting` | | `value` | `string` | #### Returns `void` --- ### useBooleanSetting ▸ **useBooleanSetting**(`uiManager`, `setting`): [`boolean`, (`newValue`: `boolean`) => `void`] Allows a react component to subscribe to changes to the given setting, interpreting its value as a boolean. #### Parameters | Name | Type | | :---------- | :----------------------------------------------------------------- | | `uiManager` | [`default`](../classes/Backend_GameLogic_GameUIManager.default.md) | | `setting` | `Setting` | #### Returns [`boolean`, (`newValue`: `boolean`) => `void`] --- ### useNumberSetting ▸ **useNumberSetting**(`uiManager`, `setting`): [`number`, (`newValue`: `number`) => `void`] Allows a react component to subscribe to changes and set the given setting as a number. Doesn't allow you to set the value of this setting to anything but a valid number. #### Parameters | Name | Type | | :---------- | :----------------------------------------------------------------- | | `uiManager` | [`default`](../classes/Backend_GameLogic_GameUIManager.default.md) | | `setting` | `Setting` | #### Returns [`number`, (`newValue`: `number`) => `void`] --- ### useSetting ▸ **useSetting**(`uiManager`, `setting`): [`string`, (`newValue`: `string` \| `undefined`) => `void`] Allows a react component to subscribe to changes and set the given setting. #### Parameters | Name | Type | | :---------- | :----------------------------------------------------------------- | | `uiManager` | [`default`](../classes/Backend_GameLogic_GameUIManager.default.md) | | `setting` | `Setting` | #### Returns [`string`, (`newValue`: `string` \| `undefined`) => `void`] ================================================ FILE: docs/modules/Frontend_Utils_ShortcutConstants.md ================================================ # Module: Frontend/Utils/ShortcutConstants ## Table of contents ### Variables - [CLOSE_MODAL](Frontend_Utils_ShortcutConstants.md#close_modal) - [EXIT_PANE](Frontend_Utils_ShortcutConstants.md#exit_pane) - [INVADE](Frontend_Utils_ShortcutConstants.md#invade) - [MINE_ARTIFACT](Frontend_Utils_ShortcutConstants.md#mine_artifact) - [MODAL_BACK_SHORTCUT](Frontend_Utils_ShortcutConstants.md#modal_back_shortcut) - [TOGGLE_ABANDON](Frontend_Utils_ShortcutConstants.md#toggle_abandon) - [TOGGLE_BROADCAST_PANE](Frontend_Utils_ShortcutConstants.md#toggle_broadcast_pane) - [TOGGLE_DIAGNOSTICS_PANE](Frontend_Utils_ShortcutConstants.md#toggle_diagnostics_pane) - [TOGGLE_EXPLORE](Frontend_Utils_ShortcutConstants.md#toggle_explore) - [TOGGLE_HAT_PANE](Frontend_Utils_ShortcutConstants.md#toggle_hat_pane) - [TOGGLE_HELP_PANE](Frontend_Utils_ShortcutConstants.md#toggle_help_pane) - [TOGGLE_PLANET_ARTIFACTS_PANE](Frontend_Utils_ShortcutConstants.md#toggle_planet_artifacts_pane) - [TOGGLE_PLANET_INFO_PANE](Frontend_Utils_ShortcutConstants.md#toggle_planet_info_pane) - [TOGGLE_PLUGINS_PANE](Frontend_Utils_ShortcutConstants.md#toggle_plugins_pane) - [TOGGLE_SEND](Frontend_Utils_ShortcutConstants.md#toggle_send) - [TOGGLE_SETTINGS_PANE](Frontend_Utils_ShortcutConstants.md#toggle_settings_pane) - [TOGGLE_TARGETTING](Frontend_Utils_ShortcutConstants.md#toggle_targetting) - [TOGGLE_TRANSACTIONS_PANE](Frontend_Utils_ShortcutConstants.md#toggle_transactions_pane) - [TOGGLE_UPGRADES_PANE](Frontend_Utils_ShortcutConstants.md#toggle_upgrades_pane) - [TOGGLE_YOUR_ARTIFACTS_PANE](Frontend_Utils_ShortcutConstants.md#toggle_your_artifacts_pane) - [TOGGLE_YOUR_PLANETS_DEX_PANE](Frontend_Utils_ShortcutConstants.md#toggle_your_planets_dex_pane) ## Variables ### CLOSE_MODAL • `Const` **CLOSE_MODAL**: `"t"` --- ### EXIT_PANE • `Const` **EXIT_PANE**: `"Escape"` --- ### INVADE • `Const` **INVADE**: `"y"` --- ### MINE_ARTIFACT • `Const` **MINE_ARTIFACT**: `"f"` --- ### MODAL_BACK_SHORTCUT • `Const` **MODAL_BACK_SHORTCUT**: `"t"` --- ### TOGGLE_ABANDON • `Const` **TOGGLE_ABANDON**: `"r"` --- ### TOGGLE_BROADCAST_PANE • `Const` **TOGGLE_BROADCAST_PANE**: `"z"` --- ### TOGGLE_DIAGNOSTICS_PANE • `Const` **TOGGLE_DIAGNOSTICS_PANE**: `"i"` --- ### TOGGLE_EXPLORE • `Const` **TOGGLE_EXPLORE**: `" "` --- ### TOGGLE_HAT_PANE • `Const` **TOGGLE_HAT_PANE**: `"x"` --- ### TOGGLE_HELP_PANE • `Const` **TOGGLE_HELP_PANE**: `"j"` --- ### TOGGLE_PLANET_ARTIFACTS_PANE • `Const` **TOGGLE_PLANET_ARTIFACTS_PANE**: `"s"` --- ### TOGGLE_PLANET_INFO_PANE • `Const` **TOGGLE_PLANET_INFO_PANE**: `"c"` --- ### TOGGLE_PLUGINS_PANE • `Const` **TOGGLE_PLUGINS_PANE**: `"k"` --- ### TOGGLE_SEND • `Const` **TOGGLE_SEND**: `"q"` --- ### TOGGLE_SETTINGS_PANE • `Const` **TOGGLE_SETTINGS_PANE**: `"h"` --- ### TOGGLE_TARGETTING • `Const` **TOGGLE_TARGETTING**: `` "`" `` --- ### TOGGLE_TRANSACTIONS_PANE • `Const` **TOGGLE_TRANSACTIONS_PANE**: `"'"` --- ### TOGGLE_UPGRADES_PANE • `Const` **TOGGLE_UPGRADES_PANE**: `"a"` --- ### TOGGLE_YOUR_ARTIFACTS_PANE • `Const` **TOGGLE_YOUR_ARTIFACTS_PANE**: `"l"` --- ### TOGGLE_YOUR_PLANETS_DEX_PANE • `Const` **TOGGLE_YOUR_PLANETS_DEX_PANE**: `";"` ================================================ FILE: docs/modules/Frontend_Utils_TerminalTypes.md ================================================ # Module: Frontend/Utils/TerminalTypes ## Table of contents ### Enumerations - [TerminalTextStyle](../enums/Frontend_Utils_TerminalTypes.TerminalTextStyle.md) ================================================ FILE: docs/modules/Frontend_Utils_TimeUtils.md ================================================ # Module: Frontend/Utils/TimeUtils ## Table of contents ### Functions - [formatDuration](Frontend_Utils_TimeUtils.md#formatduration) ## Functions ### formatDuration ▸ **formatDuration**(`durationMs`): `string` #### Parameters | Name | Type | | :----------- | :------- | | `durationMs` | `number` | #### Returns `string` ================================================ FILE: docs/modules/Frontend_Utils_UIEmitter.md ================================================ # Module: Frontend/Utils/UIEmitter ## Table of contents ### Enumerations - [UIEmitterEvent](../enums/Frontend_Utils_UIEmitter.UIEmitterEvent.md) ### Classes - [default](../classes/Frontend_Utils_UIEmitter.default.md) ================================================ FILE: docs/modules/Frontend_Utils_constants.md ================================================ # Module: Frontend/Utils/constants ## Table of contents ### Enumerations - [DFZIndex](../enums/Frontend_Utils_constants.DFZIndex.md) ### Variables - [LOCATION_ID_UB](Frontend_Utils_constants.md#location_id_ub) - [MAX_CHUNK_SIZE](Frontend_Utils_constants.md#max_chunk_size) - [MIN_CHUNK_SIZE](Frontend_Utils_constants.md#min_chunk_size) ## Variables ### LOCATION_ID_UB • `Const` **LOCATION_ID_UB**: `BigInteger` --- ### MAX_CHUNK_SIZE • `Const` **MAX_CHUNK_SIZE**: `number` **`tutorial`** to speed up the game's background rendering code, it is possible to set this value to be a higher power of two. This means that smaller chunks will be merged into larger chunks via the algorithms implemented in {@link ChunkUtils}. {@code Math.floor(Math.pow(2, 16))} should be large enough for most. --- ### MIN_CHUNK_SIZE • `Const` **MIN_CHUNK_SIZE**: `16` ================================================ FILE: docs/modules/Frontend_Utils_createDefinedContext.md ================================================ # Module: Frontend/Utils/createDefinedContext ## Table of contents ### Functions - [createDefinedContext](Frontend_Utils_createDefinedContext.md#createdefinedcontext) ## Functions ### createDefinedContext ▸ **createDefinedContext**<`T`\>(): `ContextHookWithProvider`<`T`\> Return a hook and a provider which return a value that must be defined. Normally is difficult because `React.createContext()` defaults to `undefined`. `useDefinedContext()` must be called inside of `provider`, otherwise an error will be thrown. #### Type parameters | Name | | :--- | | `T` | #### Returns `ContextHookWithProvider`<`T`\> ================================================ FILE: docs/modules/Frontend_Views_ArtifactLink.md ================================================ # Module: Frontend/Views/ArtifactLink ## Table of contents ### Functions - [ArtifactLink](Frontend_Views_ArtifactLink.md#artifactlink) ## Functions ### ArtifactLink ▸ **ArtifactLink**(`__namedParameters`): `Element` #### Parameters | Name | Type | | :----------------------------- | :--------------------------------------------------------------------- | | `__namedParameters` | `Object` | | `__namedParameters.artifact` | `Artifact` | | `__namedParameters.children` | `ReactNode` \| `ReactNode`[] | | `__namedParameters.depositOn?` | `LocationId` | | `__namedParameters.modal?` | [`ModalHandle`](../interfaces/Frontend_Views_ModalPane.ModalHandle.md) | #### Returns `Element` ================================================ FILE: docs/modules/Frontend_Views_ArtifactRow.md ================================================ # Module: Frontend/Views/ArtifactRow ## Table of contents ### Functions - [ArtifactThumb](Frontend_Views_ArtifactRow.md#artifactthumb) - [SelectArtifactRow](Frontend_Views_ArtifactRow.md#selectartifactrow) ## Functions ### ArtifactThumb ▸ **ArtifactThumb**(`__namedParameters`): `Element` #### Parameters | Name | Type | | :------------------------------------ | :------------------------------------------------ | | `__namedParameters` | `Object` | | `__namedParameters.artifact` | `Artifact` | | `__namedParameters.selectedArtifact?` | `Artifact` | | `__namedParameters.onArtifactChange?` | (`artifact`: `undefined` \| `Artifact`) => `void` | #### Returns `Element` --- ### SelectArtifactRow ▸ **SelectArtifactRow**(`__namedParameters`): `Element` #### Parameters | Name | Type | | :------------------------------------ | :------------------------------------------------ | | `__namedParameters` | `Object` | | `__namedParameters.artifacts` | `Artifact`[] | | `__namedParameters.selectedArtifact?` | `Artifact` | | `__namedParameters.onArtifactChange?` | (`artifact`: `undefined` \| `Artifact`) => `void` | #### Returns `Element` ================================================ FILE: docs/modules/Frontend_Views_CadetWormhole.md ================================================ # Module: Frontend/Views/CadetWormhole ## Table of contents ### Functions - [CadetWormhole](Frontend_Views_CadetWormhole.md#cadetwormhole) ## Functions ### CadetWormhole ▸ **CadetWormhole**(`__namedParameters`): `Element` #### Parameters | Name | Type | | :------------------------- | :------- | | `__namedParameters` | `Object` | | `__namedParameters.imgUrl` | `string` | #### Returns `Element` ================================================ FILE: docs/modules/Frontend_Views_DFErrorBoundary.md ================================================ # Module: Frontend/Views/DFErrorBoundary ## Table of contents ### Classes - [DFErrorBoundary](../classes/Frontend_Views_DFErrorBoundary.DFErrorBoundary.md) ================================================ FILE: docs/modules/Frontend_Views_DarkForestTips.md ================================================ # Module: Frontend/Views/DarkForestTips ## Table of contents ### Functions - [DarkForestTips](Frontend_Views_DarkForestTips.md#darkforesttips) - [MakeDarkForestTips](Frontend_Views_DarkForestTips.md#makedarkforesttips) ## Functions ### DarkForestTips ▸ **DarkForestTips**(`__namedParameters`): `Element` #### Parameters | Name | Type | | :------------------------- | :------------------------ | | `__namedParameters` | `Object` | | `__namedParameters.tips` | (`string` \| `Element`)[] | | `__namedParameters.title?` | `string` | #### Returns `Element` --- ### MakeDarkForestTips ▸ **MakeDarkForestTips**(`tips`): `Element` #### Parameters | Name | Type | | :----- | :--------- | | `tips` | `string`[] | #### Returns `Element` ================================================ FILE: docs/modules/Frontend_Views_EmojiPicker.md ================================================ # Module: Frontend/Views/EmojiPicker ## Table of contents ### Functions - [EmojiPicker](Frontend_Views_EmojiPicker.md#emojipicker) ## Functions ### EmojiPicker ▸ **EmojiPicker**(`__namedParameters`): `Element` #### Parameters | Name | Type | | :--------------------------- | :---------------------------- | | `__namedParameters` | `Object` | | `__namedParameters.emoji` | `undefined` \| `string` | | `__namedParameters.setEmoji` | (`emoji`: `string`) => `void` | #### Returns `Element` ================================================ FILE: docs/modules/Frontend_Views_EmojiPlanetNotification.md ================================================ # Module: Frontend/Views/EmojiPlanetNotification ## Table of contents ### Functions - [EmojiPlanetNotification](Frontend_Views_EmojiPlanetNotification.md#emojiplanetnotification) ## Functions ### EmojiPlanetNotification ▸ **EmojiPlanetNotification**(`__namedParameters`): `Element` #### Parameters | Name | Type | | :-------------------------- | :--------------------------------------------------------------------------------- | | `__namedParameters` | `Object` | | `__namedParameters.wrapper` | [`Wrapper`](../classes/Backend_Utils_Wrapper.Wrapper.md)<`undefined` \| `Planet`\> | #### Returns `Element` ================================================ FILE: docs/modules/Frontend_Views_GameWindowLayout.md ================================================ # Module: Frontend/Views/GameWindowLayout ## Table of contents ### Functions - [GameWindowLayout](Frontend_Views_GameWindowLayout.md#gamewindowlayout) ## Functions ### GameWindowLayout ▸ **GameWindowLayout**(`__namedParameters`): `Element` #### Parameters | Name | Type | | :------------------------------------- | :------------------------------- | | `__namedParameters` | `Object` | | `__namedParameters.terminalVisible` | `boolean` | | `__namedParameters.setTerminalVisible` | (`visible`: `boolean`) => `void` | #### Returns `Element` ================================================ FILE: docs/modules/Frontend_Views_GenericErrorBoundary.md ================================================ # Module: Frontend/Views/GenericErrorBoundary ## Table of contents ### Classes - [GenericErrorBoundary](../classes/Frontend_Views_GenericErrorBoundary.GenericErrorBoundary.md) ================================================ FILE: docs/modules/Frontend_Views_LandingPageRoundArt.md ================================================ # Module: Frontend/Views/LandingPageRoundArt ## Table of contents ### Functions - [LandingPageRoundArt](Frontend_Views_LandingPageRoundArt.md#landingpageroundart) ## Functions ### LandingPageRoundArt ▸ **LandingPageRoundArt**(): `Element` #### Returns `Element` ================================================ FILE: docs/modules/Frontend_Views_Leaderboard.md ================================================ # Module: Frontend/Views/Leaderboard ## Table of contents ### Functions - [LeadboardDisplay](Frontend_Views_Leaderboard.md#leadboarddisplay) ## Functions ### LeadboardDisplay ▸ **LeadboardDisplay**(): `Element` #### Returns `Element` ================================================ FILE: docs/modules/Frontend_Views_ModalIcon.md ================================================ # Module: Frontend/Views/ModalIcon ## Table of contents ### Functions - [ModalToggleButton](Frontend_Views_ModalIcon.md#modaltogglebutton) ## Functions ### ModalToggleButton ▸ **ModalToggleButton**(`__namedParameters`): `Element` A button which allows you to open a modal. #### Parameters | Name | Type | | :------------------ | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `__namedParameters` | { `hook`: [`Hook`](types_global_GlobalTypes.md#hook)<`boolean`\> ; `modal`: `ModalName` ; `style?`: `CSSProperties` ; `text?`: `string` } & `Partial`<`Omit`<[`DarkForestButton`](../classes/Frontend_Components_Btn.DarkForestButton.md), `"children"`\>\> & `Events`<{ `onClick`: (`evt`: `Event` & `MouseEvent`<[`DarkForestButton`](../classes/Frontend_Components_Btn.DarkForestButton.md), `MouseEvent`\>) => `void` }\> & `HTMLAttributes`<`HTMLElement`\> & {} & `RefAttributes`<`unknown`\> & { `hook`: [`Hook`](types_global_GlobalTypes.md#hook)<`boolean`\> ; `modal`: `ModalName` ; `style?`: `CSSProperties` ; `text?`: `string` } & `Partial`<`Omit`<[`DarkForestShortcutButton`](../classes/Frontend_Components_Btn.DarkForestShortcutButton.md), `"children"`\>\> & `Events`<{ `onClick`: (`evt`: `Event` & `MouseEvent`<[`DarkForestShortcutButton`](../classes/Frontend_Components_Btn.DarkForestShortcutButton.md), `MouseEvent`\>) => `void` ; `onShortcutPressed`: (`evt`: [`ShortcutPressedEvent`](../classes/Frontend_Components_Btn.ShortcutPressedEvent.md)) => `void` }\> & `HTMLAttributes`<`HTMLElement`\> & {} & `RefAttributes`<`unknown`\> | #### Returns `Element` ================================================ FILE: docs/modules/Frontend_Views_ModalPane.md ================================================ # Module: Frontend/Views/ModalPane ## Table of contents ### Interfaces - [ModalFrame](../interfaces/Frontend_Views_ModalPane.ModalFrame.md) - [ModalHandle](../interfaces/Frontend_Views_ModalPane.ModalHandle.md) ### Type aliases - [ModalProps](Frontend_Views_ModalPane.md#modalprops) ### Functions - [ModalPane](Frontend_Views_ModalPane.md#modalpane) ## Type aliases ### ModalProps Ƭ **ModalProps**: [`PaneProps`](Frontend_Components_GameWindowComponents.md#paneprops) & { `hideClose?`: `boolean` ; `id`: `ModalId` ; `initialPosition?`: { `x`: `number` ; `y`: `number` } ; `style?`: `CSSStyleDeclaration` & `React.CSSProperties` ; `title`: `string` \| `React.ReactNode` ; `visible`: `boolean` ; `width?`: `string` ; `helpContent?`: () => `ReactNode` ; `onClose`: () => `void` } ## Functions ### ModalPane ▸ **ModalPane**(`__namedParameters`): `null` \| `Element` #### Parameters | Name | Type | | :------------------ | :----------------------------------------------------- | | `__namedParameters` | [`ModalProps`](Frontend_Views_ModalPane.md#modalprops) | #### Returns `null` \| `Element` ================================================ FILE: docs/modules/Frontend_Views_NetworkHealth.md ================================================ # Module: Frontend/Views/NetworkHealth ## Table of contents ### Functions - [NetworkHealth](Frontend_Views_NetworkHealth.md#networkhealth) ## Functions ### NetworkHealth ▸ **NetworkHealth**(): `Element` #### Returns `Element` ================================================ FILE: docs/modules/Frontend_Views_Notifications.md ================================================ # Module: Frontend/Views/Notifications ## Table of contents ### Functions - [NotificationsPane](Frontend_Views_Notifications.md#notificationspane) ## Functions ### NotificationsPane ▸ **NotificationsPane**(): `Element` React component in charge of listening for new notifications and displaying them interactively to the user. #### Returns `Element` ================================================ FILE: docs/modules/Frontend_Views_Paused.md ================================================ # Module: Frontend/Views/Paused ## Table of contents ### Functions - [Paused](Frontend_Views_Paused.md#paused) ## Functions ### Paused ▸ **Paused**(): `Element` #### Returns `Element` ================================================ FILE: docs/modules/Frontend_Views_PlanetCard.md ================================================ # Module: Frontend/Views/PlanetCard ## Table of contents ### Functions - [PlanetCard](Frontend_Views_PlanetCard.md#planetcard) - [PlanetCardTitle](Frontend_Views_PlanetCard.md#planetcardtitle) ## Functions ### PlanetCard ▸ **PlanetCard**(`__namedParameters`): `Element` Preview basic planet information - used in `PlanetContextPane` and `HoverPlanetPane` #### Parameters | Name | Type | | :-------------------------------- | :--------------------------------------------------------------------------------- | | `__namedParameters` | `Object` | | `__namedParameters.planetWrapper` | [`Wrapper`](../classes/Backend_Utils_Wrapper.Wrapper.md)<`undefined` \| `Planet`\> | | `__namedParameters.standalone?` | `boolean` | #### Returns `Element` --- ### PlanetCardTitle ▸ **PlanetCardTitle**(`__namedParameters`): `Element` #### Parameters | Name | Type | | :------------------------- | :--------------------------------------------------------------------------------- | | `__namedParameters` | `Object` | | `__namedParameters.planet` | [`Wrapper`](../classes/Backend_Utils_Wrapper.Wrapper.md)<`undefined` \| `Planet`\> | | `__namedParameters.small?` | `boolean` | #### Returns `Element` ================================================ FILE: docs/modules/Frontend_Views_PlanetCardComponents.md ================================================ # Module: Frontend/Views/PlanetCardComponents ## Table of contents ### Variables - [TitleBar](Frontend_Views_PlanetCardComponents.md#titlebar) ### Functions - [Halved](Frontend_Views_PlanetCardComponents.md#halved) - [PlanetActiveArtifact](Frontend_Views_PlanetCardComponents.md#planetactiveartifact) - [RowTip](Frontend_Views_PlanetCardComponents.md#rowtip) - [TimesTwo](Frontend_Views_PlanetCardComponents.md#timestwo) ## Variables ### TitleBar • `Const` **TitleBar**: `StyledComponent`<`"div"`, `any`, {}, `never`\> ## Functions ### Halved ▸ **Halved**(): `Element` #### Returns `Element` --- ### PlanetActiveArtifact ▸ **PlanetActiveArtifact**(`__namedParameters`): `Element` #### Parameters | Name | Type | | :--------------------------- | :---------------------- | | `__namedParameters` | `Object` | | `__namedParameters.artifact` | `Artifact` | | `__namedParameters.planet` | `undefined` \| `Planet` | #### Returns `Element` --- ### RowTip ▸ **RowTip**(`__namedParameters`): `Element` #### Parameters | Name | Type | | :--------------------------- | :------------ | | `__namedParameters` | `Object` | | `__namedParameters.children` | `ReactNode` | | `__namedParameters.name` | `TooltipName` | #### Returns `Element` --- ### TimesTwo ▸ **TimesTwo**(): `Element` #### Returns `Element` ================================================ FILE: docs/modules/Frontend_Views_PlanetLink.md ================================================ # Module: Frontend/Views/PlanetLink ## Table of contents ### Functions - [PlanetLink](Frontend_Views_PlanetLink.md#planetlink) ## Functions ### PlanetLink ▸ **PlanetLink**(`__namedParameters`): `Element` #### Parameters | Name | Type | | :--------------------------- | :---------- | | `__namedParameters` | `Object` | | `__namedParameters.children` | `ReactNode` | | `__namedParameters.planet` | `Planet` | #### Returns `Element` ================================================ FILE: docs/modules/Frontend_Views_PlanetNotifications.md ================================================ # Module: Frontend/Views/PlanetNotifications ## Table of contents ### Enumerations - [PlanetNotifType](../enums/Frontend_Views_PlanetNotifications.PlanetNotifType.md) ### Functions - [DistanceFromCenterRow](Frontend_Views_PlanetNotifications.md#distancefromcenterrow) - [PlanetClaimedRow](Frontend_Views_PlanetNotifications.md#planetclaimedrow) - [PlanetNotifications](Frontend_Views_PlanetNotifications.md#planetnotifications) - [getNotifsForPlanet](Frontend_Views_PlanetNotifications.md#getnotifsforplanet) ## Functions ### DistanceFromCenterRow ▸ **DistanceFromCenterRow**(`__namedParameters`): `Element` #### Parameters | Name | Type | | :------------------------- | :--------------------------------------------------------------------------------- | | `__namedParameters` | `Object` | | `__namedParameters.planet` | [`Wrapper`](../classes/Backend_Utils_Wrapper.Wrapper.md)<`undefined` \| `Planet`\> | #### Returns `Element` --- ### PlanetClaimedRow ▸ **PlanetClaimedRow**(`__namedParameters`): `Element` #### Parameters | Name | Type | | :------------------------- | :--------------------------------------------------------------------------------- | | `__namedParameters` | `Object` | | `__namedParameters.planet` | [`Wrapper`](../classes/Backend_Utils_Wrapper.Wrapper.md)<`undefined` \| `Planet`\> | #### Returns `Element` --- ### PlanetNotifications ▸ **PlanetNotifications**(`__namedParameters`): `Element` #### Parameters | Name | Type | | :------------------------- | :------------------------------------------------------------------------------------ | | `__namedParameters` | `Object` | | `__namedParameters.notifs` | [`PlanetNotifType`](../enums/Frontend_Views_PlanetNotifications.PlanetNotifType.md)[] | | `__namedParameters.planet` | [`Wrapper`](../classes/Backend_Utils_Wrapper.Wrapper.md)<`undefined` \| `Planet`\> | #### Returns `Element` --- ### getNotifsForPlanet ▸ **getNotifsForPlanet**(`planet`, `account`): [`PlanetNotifType`](../enums/Frontend_Views_PlanetNotifications.PlanetNotifType.md)[] #### Parameters | Name | Type | | :-------- | :-------------------------- | | `planet` | `undefined` \| `Planet` | | `account` | `undefined` \| `EthAddress` | #### Returns [`PlanetNotifType`](../enums/Frontend_Views_PlanetNotifications.PlanetNotifType.md)[] ================================================ FILE: docs/modules/Frontend_Views_SendResources.md ================================================ # Module: Frontend/Views/SendResources ## Table of contents ### Functions - [SendResources](Frontend_Views_SendResources.md#sendresources) ## Functions ### SendResources ▸ **SendResources**(`__namedParameters`): `Element` #### Parameters | Name | Type | | :------------------------------------- | :--------------------------------------------------------------------------------- | | `__namedParameters` | `Object` | | `__namedParameters.planetWrapper` | [`Wrapper`](../classes/Backend_Utils_Wrapper.Wrapper.md)<`undefined` \| `Planet`\> | | `__namedParameters.onToggleAbandon` | () => `void` | | `__namedParameters.onToggleSendForces` | () => `void` | #### Returns `Element` ================================================ FILE: docs/modules/Frontend_Views_Share.md ================================================ # Module: Frontend/Views/Share ## Table of contents ### Interfaces - [ShareProps](../interfaces/Frontend_Views_Share.ShareProps.md) ### Functions - [Share](Frontend_Views_Share.md#share) ## Functions ### Share ▸ **Share**<`T`\>(`props`): `Element` Helper component that allows you to load data from the contract, as if it was viewed from a particular account. Allows you to switch accounts. Just pass in: 1. a function that loads the data you want, given a [[ReaderDataStore]] 2. a function that renders the given data with React ... and this component will take care of loading what you want. #### Type parameters | Name | | :--- | | `T` | #### Parameters | Name | Type | | :------ | :--------------------------------------------------------------------- | | `props` | [`ShareProps`](../interfaces/Frontend_Views_Share.ShareProps.md)<`T`\> | #### Returns `Element` ================================================ FILE: docs/modules/Frontend_Views_SidebarPane.md ================================================ # Module: Frontend/Views/SidebarPane ## Table of contents ### Functions - [SidebarPane](Frontend_Views_SidebarPane.md#sidebarpane) ## Functions ### SidebarPane ▸ **SidebarPane**(`__namedParameters`): `Element` #### Parameters | Name | Type | | :------------------------------------- | :----------------------------------------------------- | | `__namedParameters` | `Object` | | `__namedParameters.helpHook` | [`Hook`](types_global_GlobalTypes.md#hook)<`boolean`\> | | `__namedParameters.planetdexHook` | [`Hook`](types_global_GlobalTypes.md#hook)<`boolean`\> | | `__namedParameters.pluginsHook` | [`Hook`](types_global_GlobalTypes.md#hook)<`boolean`\> | | `__namedParameters.settingsHook` | [`Hook`](types_global_GlobalTypes.md#hook)<`boolean`\> | | `__namedParameters.transactionLogHook` | [`Hook`](types_global_GlobalTypes.md#hook)<`boolean`\> | | `__namedParameters.yourArtifactsHook` | [`Hook`](types_global_GlobalTypes.md#hook)<`boolean`\> | #### Returns `Element` ================================================ FILE: docs/modules/Frontend_Views_SortableTable.md ================================================ # Module: Frontend/Views/SortableTable ## Table of contents ### Functions - [SortableTable](Frontend_Views_SortableTable.md#sortabletable) ## Functions ### SortableTable ▸ **SortableTable**<`T`\>(`__namedParameters`): `Element` #### Type parameters | Name | | :--- | | `T` | #### Parameters | Name | Type | | :-------------------------------- | :----------------------------------------- | | `__namedParameters` | `Object` | | `__namedParameters.alignments?` | (`"r"` \| `"l"` \| `"c"`)[] | | `__namedParameters.columns` | (`t`: `T`, `i`: `number`) => `ReactNode`[] | | `__namedParameters.headers` | `ReactNode`[] | | `__namedParameters.paginated?` | `boolean` | | `__namedParameters.rows` | `T`[] | | `__namedParameters.sortFunctions` | (`left`: `T`, `right`: `T`) => `number`[] | #### Returns `Element` ================================================ FILE: docs/modules/Frontend_Views_TabbedView.md ================================================ # Module: Frontend/Views/TabbedView ## Table of contents ### Functions - [TabbedView](Frontend_Views_TabbedView.md#tabbedview) ## Functions ### TabbedView ▸ **TabbedView**(`__namedParameters`): `Element` This component allows you to render several tabs of content. Each tab can be selected for viewing by clicking on its corresponding tab button. Useful for displaying lots of slightly different but related information to the user. #### Parameters | Name | Type | | :------------------------------ | :------------------------------------ | | `__namedParameters` | `Object` | | `__namedParameters.style?` | `CSSProperties` | | `__namedParameters.tabTitles` | `string`[] | | `__namedParameters.tabContents` | (`tabIndex`: `number`) => `ReactNode` | #### Returns `Element` ================================================ FILE: docs/modules/Frontend_Views_Table.md ================================================ # Module: Frontend/Views/Table ## Table of contents ### Functions - [Table](Frontend_Views_Table.md#table) ## Functions ### Table ▸ **Table**<`T`\>(`__namedParameters`): `Element` React api for creating tables. #### Type parameters | Name | | :--- | | `T` | #### Parameters | Name | Type | | :------------------------------- | :----------------------------------------- | | `__namedParameters` | `Object` | | `__namedParameters.alignments?` | (`"r"` \| `"l"` \| `"c"`)[] | | `__namedParameters.columns` | (`t`: `T`, `i`: `number`) => `ReactNode`[] | | `__namedParameters.headerStyle?` | `CSSProperties` | | `__namedParameters.headers` | `ReactNode`[] | | `__namedParameters.paginated?` | `boolean` | | `__namedParameters.rows` | `T`[] | #### Returns `Element` ================================================ FILE: docs/modules/Frontend_Views_Terminal.md ================================================ # Module: Frontend/Views/Terminal ## Table of contents ### Interfaces - [TerminalHandle](../interfaces/Frontend_Views_Terminal.TerminalHandle.md) - [TerminalProps](../interfaces/Frontend_Views_Terminal.TerminalProps.md) ### Variables - [Terminal](Frontend_Views_Terminal.md#terminal) ## Variables ### Terminal • `Const` **Terminal**: `ForwardRefExoticComponent`<[`TerminalProps`](../interfaces/Frontend_Views_Terminal.TerminalProps.md) & `RefAttributes`<`undefined` \| [`TerminalHandle`](../interfaces/Frontend_Views_Terminal.TerminalHandle.md)\>\> ================================================ FILE: docs/modules/Frontend_Views_TopBar.md ================================================ # Module: Frontend/Views/TopBar ## Table of contents ### Functions - [TopBar](Frontend_Views_TopBar.md#topbar) ## Functions ### TopBar ▸ **TopBar**(`__namedParameters`): `Element` #### Parameters | Name | Type | | :------------------------------------ | :----------------------------------------------------- | | `__namedParameters` | `Object` | | `__namedParameters.twitterVerifyHook` | [`Hook`](types_global_GlobalTypes.md#hook)<`boolean`\> | #### Returns `Element` ================================================ FILE: docs/modules/Frontend_Views_UpgradePreview.md ================================================ # Module: Frontend/Views/UpgradePreview ## Table of contents ### Functions - [UpgradePreview](Frontend_Views_UpgradePreview.md#upgradepreview) ## Functions ### UpgradePreview ▸ **UpgradePreview**(`__namedParameters`): `Element` #### Parameters | Name | Type | | :------------------------------ | :--------------------------------- | | `__namedParameters` | `Object` | | `__namedParameters.branchName` | `undefined` \| `UpgradeBranchName` | | `__namedParameters.cantUpgrade` | `boolean` | | `__namedParameters.planet` | `undefined` \| `Planet` | | `__namedParameters.upgrade` | `undefined` \| `Upgrade` | #### Returns `Element` ================================================ FILE: docs/modules/Frontend_Views_WithdrawSilver.md ================================================ # Module: Frontend/Views/WithdrawSilver ## Table of contents ### Functions - [WithdrawSilver](Frontend_Views_WithdrawSilver.md#withdrawsilver) ## Functions ### WithdrawSilver ▸ **WithdrawSilver**(`__namedParameters`): `Element` #### Parameters | Name | Type | | :-------------------------- | :--------------------------------------------------------------------------------- | | `__namedParameters` | `Object` | | `__namedParameters.wrapper` | [`Wrapper`](../classes/Backend_Utils_Wrapper.Wrapper.md)<`undefined` \| `Planet`\> | #### Returns `Element` ================================================ FILE: docs/modules/types_darkforest_api_ChunkStoreTypes.md ================================================ # Module: \_types/darkforest/api/ChunkStoreTypes ## Table of contents ### Interfaces - [ChunkStore](../interfaces/types_darkforest_api_ChunkStoreTypes.ChunkStore.md) - [PersistedChunk](../interfaces/types_darkforest_api_ChunkStoreTypes.PersistedChunk.md) - [PersistedLocation](../interfaces/types_darkforest_api_ChunkStoreTypes.PersistedLocation.md) ### Type aliases - [BucketId](types_darkforest_api_ChunkStoreTypes.md#bucketid) - [ChunkId](types_darkforest_api_ChunkStoreTypes.md#chunkid) ## Type aliases ### BucketId Ƭ **BucketId**: `Abstract`<`string`, `"BucketId"`\> one of "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ" --- ### ChunkId Ƭ **ChunkId**: `Abstract`<`string`, `"ChunkId"`\> Don't worry about the values here. Never base code off the values here. PLEASE. ================================================ FILE: docs/modules/types_darkforest_api_ContractsAPITypes.md ================================================ # Module: \_types/darkforest/api/ContractsAPITypes ## Table of contents ### Enumerations - [ContractEvent](../enums/types_darkforest_api_ContractsAPITypes.ContractEvent.md) - [ContractsAPIEvent](../enums/types_darkforest_api_ContractsAPITypes.ContractsAPIEvent.md) - [InitArgIdxs](../enums/types_darkforest_api_ContractsAPITypes.InitArgIdxs.md) - [MoveArgIdxs](../enums/types_darkforest_api_ContractsAPITypes.MoveArgIdxs.md) - [PlanetEventType](../enums/types_darkforest_api_ContractsAPITypes.PlanetEventType.md) - [UpgradeArgIdxs](../enums/types_darkforest_api_ContractsAPITypes.UpgradeArgIdxs.md) - [ZKArgIdx](../enums/types_darkforest_api_ContractsAPITypes.ZKArgIdx.md) ### Interfaces - [ContractConstants](../interfaces/types_darkforest_api_ContractsAPITypes.ContractConstants.md) ### Type aliases - [ClaimArgs](types_darkforest_api_ContractsAPITypes.md#claimargs) - [ClientMockchainData](types_darkforest_api_ContractsAPITypes.md#clientmockchaindata) - [DepositArtifactArgs](types_darkforest_api_ContractsAPITypes.md#depositartifactargs) - [MoveArgs](types_darkforest_api_ContractsAPITypes.md#moveargs) - [PlanetTypeWeights](types_darkforest_api_ContractsAPITypes.md#planettypeweights) - [PlanetTypeWeightsByLevel](types_darkforest_api_ContractsAPITypes.md#planettypeweightsbylevel) - [PlanetTypeWeightsBySpaceType](types_darkforest_api_ContractsAPITypes.md#planettypeweightsbyspacetype) - [UpgradeArgs](types_darkforest_api_ContractsAPITypes.md#upgradeargs) - [WhitelistArgs](types_darkforest_api_ContractsAPITypes.md#whitelistargs) - [WithdrawArtifactArgs](types_darkforest_api_ContractsAPITypes.md#withdrawartifactargs) ## Type aliases ### ClaimArgs Ƭ **ClaimArgs**: [[`string`, `string`], [[`string`, `string`], [`string`, `string`]], [`string`, `string`], [`string`, `string`, `string`, `string`, `string`, `string`, `string`, `string`, `string`]] --- ### ClientMockchainData Ƭ **ClientMockchainData**: `null` \| `undefined` \| `number` \| `string` \| `boolean` \| `EthersBN` \| [`ClientMockchainData`](types_darkforest_api_ContractsAPITypes.md#clientmockchaindata)[] \| { [key in string \| number]: ClientMockchainData } --- ### DepositArtifactArgs Ƭ **DepositArtifactArgs**: [`string`, `string`] --- ### MoveArgs Ƭ **MoveArgs**: [[`string`, `string`], [[`string`, `string`], [`string`, `string`]], [`string`, `string`], [`string`, `string`, `string`, `string`, `string`, `string`, `string`, `string`, `string`, `string`, `string`, `string`, `string`, `string`]] --- ### PlanetTypeWeights Ƭ **PlanetTypeWeights**: [`number`, `number`, `number`, `number`, `number`] --- ### PlanetTypeWeightsByLevel Ƭ **PlanetTypeWeightsByLevel**: [[`PlanetTypeWeights`](types_darkforest_api_ContractsAPITypes.md#planettypeweights), [`PlanetTypeWeights`](types_darkforest_api_ContractsAPITypes.md#planettypeweights), [`PlanetTypeWeights`](types_darkforest_api_ContractsAPITypes.md#planettypeweights), [`PlanetTypeWeights`](types_darkforest_api_ContractsAPITypes.md#planettypeweights), [`PlanetTypeWeights`](types_darkforest_api_ContractsAPITypes.md#planettypeweights), [`PlanetTypeWeights`](types_darkforest_api_ContractsAPITypes.md#planettypeweights), [`PlanetTypeWeights`](types_darkforest_api_ContractsAPITypes.md#planettypeweights), [`PlanetTypeWeights`](types_darkforest_api_ContractsAPITypes.md#planettypeweights), [`PlanetTypeWeights`](types_darkforest_api_ContractsAPITypes.md#planettypeweights), [`PlanetTypeWeights`](types_darkforest_api_ContractsAPITypes.md#planettypeweights)] --- ### PlanetTypeWeightsBySpaceType Ƭ **PlanetTypeWeightsBySpaceType**: [[`PlanetTypeWeightsByLevel`](types_darkforest_api_ContractsAPITypes.md#planettypeweightsbylevel), [`PlanetTypeWeightsByLevel`](types_darkforest_api_ContractsAPITypes.md#planettypeweightsbylevel), [`PlanetTypeWeightsByLevel`](types_darkforest_api_ContractsAPITypes.md#planettypeweightsbylevel), [`PlanetTypeWeightsByLevel`](types_darkforest_api_ContractsAPITypes.md#planettypeweightsbylevel)] --- ### UpgradeArgs Ƭ **UpgradeArgs**: [`string`, `string`] --- ### WhitelistArgs Ƭ **WhitelistArgs**: [`string`, `string`] --- ### WithdrawArtifactArgs Ƭ **WithdrawArtifactArgs**: [`string`, `string`] ================================================ FILE: docs/modules/types_darkforest_api_UtilityServerAPITypes.md ================================================ # Module: \_types/darkforest/api/UtilityServerAPITypes ## Table of contents ### Type aliases - [AddressTwitterMap](types_darkforest_api_UtilityServerAPITypes.md#addresstwittermap) ## Type aliases ### AddressTwitterMap Ƭ **AddressTwitterMap**: `Object` #### Index signature ▪ [ethAddress: `string`]: `string` ================================================ FILE: docs/modules/types_file_loader_FileWorkerTypes.__darkforest_eth_contracts_abis___json_.md ================================================ # Namespace: "@darkforest_eth/contracts/abis/\*.json" [\_types/file-loader/FileWorkerTypes](types_file_loader_FileWorkerTypes.md)."@darkforest_eth/contracts/abis/\*.json" ## Table of contents ### Variables - [default](types_file_loader_FileWorkerTypes.__darkforest_eth_contracts_abis___json_.md#default) ## Variables ### default • `Const` **default**: `string` ================================================ FILE: docs/modules/types_file_loader_FileWorkerTypes.__darkforest_eth_snarks___wasm_.md ================================================ # Namespace: "@darkforest_eth/snarks/\*.wasm" [\_types/file-loader/FileWorkerTypes](types_file_loader_FileWorkerTypes.md)."@darkforest_eth/snarks/\*.wasm" ## Table of contents ### Variables - [default](types_file_loader_FileWorkerTypes.__darkforest_eth_snarks___wasm_.md#default) ## Variables ### default • `Const` **default**: `string` ================================================ FILE: docs/modules/types_file_loader_FileWorkerTypes.__darkforest_eth_snarks___zkey_.md ================================================ # Namespace: "@darkforest_eth/snarks/\*.zkey" [\_types/file-loader/FileWorkerTypes](types_file_loader_FileWorkerTypes.md)."@darkforest_eth/snarks/\*.zkey" ## Table of contents ### Variables - [default](types_file_loader_FileWorkerTypes.__darkforest_eth_snarks___zkey_.md#default) ## Variables ### default • `Const` **default**: `string` ================================================ FILE: docs/modules/types_file_loader_FileWorkerTypes.md ================================================ # Module: \_types/file-loader/FileWorkerTypes ## Table of contents ### Namespaces - ["@darkforest_eth/contracts/abis/\*.json"](types_file_loader_FileWorkerTypes.__darkforest_eth_contracts_abis___json_.md) - ["@darkforest_eth/snarks/\*.wasm"](types_file_loader_FileWorkerTypes.__darkforest_eth_snarks___wasm_.md) - ["@darkforest_eth/snarks/\*.zkey"](types_file_loader_FileWorkerTypes.__darkforest_eth_snarks___zkey_.md) ================================================ FILE: docs/modules/types_global_GlobalTypes.md ================================================ # Module: \_types/global/GlobalTypes ## Table of contents ### Enumerations - [StatIdx](../enums/types_global_GlobalTypes.StatIdx.md) ### Interfaces - [ClaimCountdownInfo](../interfaces/types_global_GlobalTypes.ClaimCountdownInfo.md) - [MinerWorkerMessage](../interfaces/types_global_GlobalTypes.MinerWorkerMessage.md) - [RevealCountdownInfo](../interfaces/types_global_GlobalTypes.RevealCountdownInfo.md) ### Type aliases - [HashConfig](types_global_GlobalTypes.md#hashconfig) - [Hook](types_global_GlobalTypes.md#hook) ## Type aliases ### HashConfig Ƭ **HashConfig**: `Object` #### Type declaration | Name | Type | | :------------------ | :-------- | | `biomebaseKey` | `number` | | `perlinLengthScale` | `number` | | `perlinMirrorX` | `boolean` | | `perlinMirrorY` | `boolean` | | `planetHashKey` | `number` | | `planetRarity` | `number` | | `spaceTypeKey` | `number` | --- ### Hook Ƭ **Hook**<`T`\>: [`T`, `Dispatch`<`SetStateAction`<`T`\>\>] #### Type parameters | Name | | :--- | | `T` | ================================================ FILE: embedded_plugins/Admin-Controls.ts ================================================ // organize-imports-ignore import type { EthAddress, LocatablePlanet, LocationId, Planet } from '@darkforest_eth/types'; import { MAX_ARTIFACT_RARITY, MAX_SPACESHIP_TYPE, MIN_ARTIFACT_RARITY, MIN_ARTIFACT_TYPE, MIN_SPACESHIP_TYPE, MIN_BIOME, MAX_BIOME, //@ts-ignore } from 'https://cdn.skypack.dev/@darkforest_eth/constants'; //@ts-ignore import { getPlanetNameHash } from 'https://cdn.skypack.dev/@darkforest_eth/procedural'; import { locationIdToDecStr, artifactIdFromHexStr, locationIdFromDecStr, //@ts-ignore } from 'https://cdn.skypack.dev/@darkforest_eth/serde'; import { ArtifactRarityNames, ArtifactType, ArtifactTypeNames, BiomeNames, Player, PlanetType, PlanetTypeNames, WorldCoords, //@ts-ignore } from 'https://cdn.skypack.dev/@darkforest_eth/types'; import { html, render, useEffect, useState, useCallback, //@ts-ignore } from 'https://unpkg.com/htm/preact/standalone.module.js'; function random256Id() { const alphabet = '0123456789ABCDEF'.split(''); let result = '0x'; for (let i = 0; i < 256 / 4; i++) { result += alphabet[Math.floor(Math.random() * alphabet.length)]; } return result; } async function createArtifact( owner: EthAddress, type: ArtifactType, planet: Planet, rarity: string, biome: string ) { if (!owner) { alert('no account'); return; } const tokenId = random256Id(); // see contracts/types/ActionTypes.sol - CreateArtifactArgs const args = Promise.resolve([ { tokenId, discoverer: owner, planetId: locationIdToDecStr(planet.locationId), rarity, biome, artifactType: type, owner: owner, controller: '0x0000000000000000000000000000000000000000', }, ]); const tx = await df.submitTransaction({ args, contract: df.getContract(), methodName: 'adminGiveArtifact', }); tx.confirmedPromise.then(() => { df.hardRefreshArtifact(artifactIdFromHexStr(tokenId.slice(2))); df.hardRefreshPlanet(planet.locationId); }); return tx; } async function initPlanet(planet: LocatablePlanet) { if (planet.isInContract) return; const args = Promise.resolve([locationIdToDecStr(planet.locationId), planet.perlin]); const tx = await df.submitTransaction({ args, contract: df.getContract(), methodName: 'adminInitializePlanet', }); await tx.confirmedPromise; return tx; } async function spawnSpaceship( planet: LocatablePlanet | undefined, owner: EthAddress | undefined, shipType: ArtifactType ) { if (!owner) { alert('no account'); return; } if (!planet) { alert('no selected planet'); return; } await initPlanet(planet); const args = Promise.resolve([locationIdToDecStr(planet.locationId), owner, shipType]); const tx = await df.submitTransaction({ args, contract: df.getContract(), methodName: 'adminGiveSpaceShip', }); tx.confirmedPromise.then(() => df.hardRefreshPlanet(planet.locationId)); return tx; } async function takeOwnership( planet: LocatablePlanet | undefined, newOwner: EthAddress | undefined ) { if (!newOwner) { alert('no account'); return; } if (!planet) { alert('no selected planet'); return; } const snarkArgs = await df.getSnarkHelper().getInitArgs( planet.location.coords.x, planet.location.coords.y, Math.floor(Math.sqrt(planet.location.coords.x ** 2 + planet.location.coords.y ** 2)) + 1 // floor(sqrt(x^2 + y^2)) + 1 ); const args = Promise.resolve([newOwner, ...snarkArgs]); const tx = await df.submitTransaction({ locationId: planet.locationId, newOwner, args, contract: df.getContract(), methodName: 'safeSetOwner', }); tx.confirmedPromise.then(() => df.hardRefreshPlanet(planet.locationId)); return tx; } async function pauseGame() { const tx = await df.submitTransaction({ args: Promise.resolve([]), contract: df.getContract(), methodName: 'pause', }); return tx; } async function unpauseGame() { const tx = await df.submitTransaction({ args: Promise.resolve([]), contract: df.getContract(), methodName: 'unpause', }); return tx; } async function addAddressToWhitelist(address: EthAddress) { const args = Promise.resolve([address]); const tx = await df.submitTransaction({ args, contract: df.getContract(), methodName: 'addToWhitelist', }); return tx; } async function createPlanet(coords: WorldCoords, level: number, type: PlanetType) { coords.x = Math.round(coords.x); coords.y = Math.round(coords.y); const location = df.locationBigIntFromCoords(coords).toString(); const perlinValue = df.biomebasePerlin(coords, true); const args = Promise.resolve([ { x: coords.x, y: coords.y, level, planetType: type, requireValidLocationId: false, location: location, perlin: perlinValue, }, ]); const tx = await df.submitTransaction({ args, contract: df.getContract(), methodName: 'createPlanet', }); await tx.confirmedPromise; const revealArgs = df.getSnarkHelper().getRevealArgs(coords.x, coords.y); const revealTx = await df.submitTransaction({ args: revealArgs, contract: df.getContract(), methodName: 'revealLocation', }); await revealTx.confirmedPromise; await df.hardRefreshPlanet(locationIdFromDecStr(location)); } function PlanetLink({ planetId }: { planetId?: LocationId }) { if (planetId) { return html` ui.centerLocationId(planetId)} > ${getPlanetNameHash(planetId)} `; } else { return '(none selected)'; } } function Heading({ title }: { title: string }) { return html`

${title}

`; } function shipOptions() { const options = [] as HTMLOptionElement[]; for (let i = MIN_SPACESHIP_TYPE; i <= MAX_SPACESHIP_TYPE; i++) { options.push(html``); } return options; } function artifactOptions() { const options = [] as HTMLOptionElement[]; for (let i = MIN_ARTIFACT_TYPE; i < MIN_SPACESHIP_TYPE; i++) { options.push(html``); } return options; } function artifactRarityOptions() { const options = [] as HTMLOptionElement[]; for (let i = MIN_ARTIFACT_RARITY; i <= MAX_ARTIFACT_RARITY; i++) { options.push(html``); } return options; } function artifactBiomeOptions() { const options = [] as HTMLOptionElement[]; for (let i = MIN_BIOME; i <= MAX_BIOME; i++) { options.push(html``); } return options; } function accountOptions(players: Player[]) { const options = [] as HTMLOptionElement[]; for (const player of players) { options.push( html`` ); } return options; } function planetTypeOptions() { const options = [] as HTMLOptionElement[]; for (let i = 0; i <= Object.values(PlanetType).length - 1; i++) { options.push(html``); } return options; } function Select({ style, value, onChange, items, }: { style: Record; value: string; onChange: (e: InputEvent) => void; items: unknown[]; }) { return html` `; } const wrapperStyle = { display: 'flex', flexDirection: 'column', gap: '8px', }; const rowStyle = { display: 'flex', gap: '8px', alignItems: 'center', }; function PlanetCreator() { const uiEmitter = ui.getUIEmitter(); const [level, setLevel] = useState(0); const [planetType, setPlanetType] = useState(PlanetType.PLANET); const [choosingLocation, setChoosingLocation] = useState(false); const [planetCoords, setPlanetCoords] = useState(null); const placePlanet = useCallback( (coords: WorldCoords) => { createPlanet(coords, parseInt(level), planetType); setChoosingLocation(false); }, [level, planetType, setChoosingLocation] ); const updatePlanetCoords = useCallback( (coords: WorldCoords) => { setPlanetCoords(coords); }, [setPlanetCoords] ); useEffect(() => { if (choosingLocation) { uiEmitter.on('WorldMouseClick', placePlanet); uiEmitter.on('WorldMouseMove', updatePlanetCoords); return () => { uiEmitter.off('WorldMouseClick', placePlanet); uiEmitter.off('WorldMouseMove', updatePlanetCoords); }; } return () => {}; }, [uiEmitter, choosingLocation, placePlanet, updatePlanetCoords]); return html`

Create Planet

setLevel((e.target as HTMLInputElement).value)} max=${9} >
<${Select} id="planet-type-selector" value=${planetType} onChange=${(e: InputEvent) => setPlanetType((e.target as HTMLSelectElement).value)} items=${planetTypeOptions()} />
${!choosingLocation && html` { setChoosingLocation(true); }} > Choose Planet Location `} ${choosingLocation && html`

Creating planet on coords
(${Math.round(planetCoords?.x)}, ${Math.round(planetCoords?.y)})

`} ${choosingLocation && html` setChoosingLocation(false)}> Cancel Creation`}
`; } function App() { const [selectedPlanet, setSelectedPlanet] = useState(null); const [selectedShip, setSelectedShip] = useState(MIN_SPACESHIP_TYPE); const [selectedArtifact, setSelectedArtifact] = useState(MIN_ARTIFACT_TYPE); const [artifactRarity, setArtifactRarity] = useState('1'); const [artifactBiome, setArtifactBiome] = useState(MIN_BIOME.toString()); const [whitelistAddress, setWhitelistAddress] = useState(null); const [account, setAccount] = useState(null); const [targetAccount, setTargetAccount] = useState(null); const [allPlayers, setAllPlayers] = useState([]); useEffect(() => { const account = df.getAccount(); setAccount(account); setTargetAccount(account); }, []); useEffect(() => { const refreshPlayers = () => { setAllPlayers(df.getAllPlayers()); }; const sub = df.playersUpdated$.subscribe(refreshPlayers); refreshPlayers(); return () => sub.unsubscribe(); }, []); useEffect(() => { const subscription = ui.selectedPlanetId$.subscribe((p: LocationId) => { setSelectedPlanet(ui.getPlanetWithId(p)); }); return () => subscription.unsubscribe(); }, [setSelectedPlanet]); return html`

Logged in as account: ${account}

<${Heading} title="Game state" />
Change game state: pauseGame()}> Pause unpauseGame()}> Unpause
<${Heading} title="Whitelist players" />
setWhitelistAddress((e.target as HTMLInputElement).value)} placeholder="Address to whitelist" > addAddressToWhitelist(whitelistAddress)}> Whitelist Address
<${Heading} title="Give Planets" />
Planet: <${PlanetLink} planetId=${(selectedPlanet as Planet)?.locationId} /> to <${Select} style=${{ flex: '1' }} value=${targetAccount} onChange=${(e: InputEvent) => setTargetAccount((e.target as HTMLSelectElement).value)} items=${accountOptions(allPlayers)} /> takeOwnership(selectedPlanet, targetAccount)}> Give Planet
<${Heading} title="Give Spaceships" />
<${Select} style=${{ flex: '1' }} value=${selectedShip} onChange=${(e: InputEvent) => setSelectedShip((e.target as HTMLSelectElement).value)} items=${shipOptions()} /> to <${Select} style=${{ flex: '1' }} value=${targetAccount} onChange=${(e: InputEvent) => setTargetAccount((e.target as HTMLSelectElement).value)} items=${accountOptions(allPlayers)} />
${'On planet: '} <${PlanetLink} planetId=${(selectedPlanet as Planet)?.locationId} /> spawnSpaceship(selectedPlanet, targetAccount, selectedShip)}> Spawn Spaceship
<${Heading} title="Give Artifacts" />
<${Select} style=${{ flex: '1' }} value=${artifactRarity} onChange=${(e: InputEvent) => setArtifactRarity((e.target as HTMLSelectElement).value)} items=${artifactRarityOptions()} /> <${Select} style=${{ flex: '1' }} value=${artifactBiome} onChange=${(e: InputEvent) => setArtifactBiome((e.target as HTMLSelectElement).value)} items=${artifactBiomeOptions()} /> <${Select} style=${{ flex: '1' }} value=${selectedArtifact} onChange=${(e: InputEvent) => setSelectedArtifact((e.target as HTMLSelectElement).value)} items=${artifactOptions()} /> to <${Select} style=${{ flex: '1' }} value=${targetAccount} onChange=${(e: InputEvent) => setTargetAccount((e.target as HTMLSelectElement).value)} items=${accountOptions(allPlayers)} />
${'On planet: '} <${PlanetLink} planetId=${(selectedPlanet as Planet)?.locationId} /> createArtifact( targetAccount, selectedArtifact, selectedPlanet, artifactRarity, artifactBiome )} > Give Artifact
<${PlanetCreator} />
`; } class Plugin implements DFPlugin { async render(container: HTMLDivElement) { container.style.width = '525px'; render(html`<${App} />`, container); } } export default Plugin; ================================================ FILE: embedded_plugins/Getting-Started.ts ================================================ /** * Hi there! * * Looks like you've found the Dark Forest plugins system. * Read through this script to learn how to write plugins! * * Most importantly, you have access these globals: * 1. df - Just like the df object in your console. * 2. ui - For interacting with the game's user interface. * * Let's log these to the console when you run your plugin! */ console.log(df, ui); /** * Plugins are just TypeScript (or modern JavaScript, if you prefer), so you can use imports, too! */ // @ts-ignore import confetti from 'https://cdn.skypack.dev/canvas-confetti'; /** * A plugin is a Class with render and destroy methods. * Other than that, you are free to do whatever, so be careful! */ class Readme implements DFPlugin { private canvas: HTMLCanvasElement; /** * A constructor can be used to keep track of information. */ constructor() { this.canvas = document.createElement('canvas'); this.canvas.width = 400; this.canvas.height = 150; } /** * A plugin's render function is called once. * Here, you can insert custom html into a game modal. * You render any sort of UI that makes sense for the plugin! */ async render(div: HTMLDivElement) { div.style.width = '400px'; const firstTextDiv = document.createElement('div'); firstTextDiv.innerText = 'This is an example plugin. Check out its source by' + ' clicking "edit" button that is to the right of the' + ' README plugin in the Plugin Manager modal! '; const secondTextDiv = document.createElement('div'); secondTextDiv.innerText = '... Or, click the button below to get a free artifact!'; const myButton = document.createElement('df-button'); myButton.innerText = 'give me an artifact'; myButton.addEventListener('click', async () => { await confetti.create(this.canvas)({ origin: { x: 0.5, y: 1 }, }); const ctx = this.canvas.getContext('2d'); if (ctx) { ctx.fillStyle = 'white'; ctx.font = '20px Sans-serif'; ctx.fillText('Gotcha!', 150, 60); } }); div.appendChild(firstTextDiv); div.appendChild(document.createElement('br')); div.appendChild(secondTextDiv); div.appendChild(document.createElement('br')); div.appendChild(this.canvas); div.appendChild(myButton); } /** * When this is unloaded, the game calls the destroy method. * So you can clean up everything nicely! */ destroy() { const ctx = this.canvas.getContext('2d'); if (ctx) { ctx.clearRect(0, 0, this.canvas.width, this.canvas.height); } } } /** * For the game to know about your plugin, you must export it! * * Use `export default` to expose your plugin Class. */ export default Readme; ================================================ FILE: embedded_plugins/Locate-Artifacts.ts ================================================ /** * Remember, you have access these globals: * 1. df - Just like the df object in your console. * 2. ui - For interacting with the game's user interface. * * Let's log these to the console when you run your plugin! */ console.log(df, ui); class ArtifactsFinder implements DFPlugin { private planetList: HTMLDivElement; renderPlanets = () => { this.planetList.innerHTML = ''; // keep track of how many we have found let count = 0; const countText = document.createElement('div'); this.planetList.appendChild(countText); for (const planet of df.getAllPlanets()) { // @ts-ignore if (planet.location) { if (df.isPlanetMineable(planet)) { // is there any other filtering you'd want to do? // sometimes planets have artifacts deposited on them! // somtimes a planet's artifact has already been mined. // see if you can modify this plugin to make it do what // you want! const planetEntry = document.createElement('div'); this.planetList.appendChild(planetEntry); // hint: have a hard time finding planets? // ui.centerCoords might help... planetEntry.innerText = '(' + // @ts-ignore planet.location.coords.x + ', ' + // @ts-ignore planet.location.coords.y + ')'; count++; } } } if (count === 0) { countText.innerText = 'you have not found any artifacts yet'; } else { countText.innerText = 'you have found ' + count + ' artifacts'; } }; async render(container: HTMLDivElement) { console.log('rendered 1 artifacts finder'); const findArtifactsButton = document.createElement('df-button'); findArtifactsButton.innerText = 'find me some artifacts!'; container.appendChild(findArtifactsButton); container.appendChild(document.createElement('br')); container.appendChild(document.createElement('br')); findArtifactsButton.addEventListener('click', this.renderPlanets); this.planetList = document.createElement('div'); container.appendChild(this.planetList); this.planetList.style.maxHeight = '300px'; this.planetList.style.width = '400px'; this.planetList.style.overflowX = 'hidden'; this.planetList.style.overflowY = 'scroll'; console.log('rendered artifacts finder'); } } /** * And don't forget to export it! */ export default ArtifactsFinder; ================================================ FILE: embedded_plugins/Rage-Cage.ts ================================================ class RageCage implements DFPlugin { private img: HTMLImageElement; private loaded: boolean; /** * As you saw in the README plugin, you can render * arbitrary HTML UI into a Dark Forest modal. */ async render(div: HTMLDivElement) { // once this plugin is run, let's load a nice image // from the internet, in order to draw it on top of // planets this.img = document.createElement('img'); this.loaded = false; div.appendChild(this.img); this.img.addEventListener('load', () => { // we should only use the image once // it actually loads. this.loaded = true; div.innerText = 'welcome to nicolas cage world'; }); this.img.src = 'https://upload.wikimedia.org/wikipedia/' + 'commons/c/c0/Nicolas_Cage_Deauville_2013.jpg'; // hide the image, it doesn't need to show up // in the modal, we only need this img element // to load the image. this.img.style.display = 'none'; div.style.width = '100px'; div.style.height = '100px'; div.innerText = 'loading, please wait!'; // check out the helpful functions that appear // in the Viewport class! console.log(ui.getViewport()); } /** * In addition to rendering HTML UI into a div, plugins * can draw directly onto the game UI. This function is * optional, but if it exists, it is called in sync with * the rest of the game, and allows you to draw onto an * HTML5 canvas that lays on top of the rest of the game. * * In the example below, we render an image on top of every * planet. * * ctx is an instance of CanvasRenderingContext2D. */ draw(ctx: CanvasRenderingContext2D) { // don't draw anything until nic cage loads if (!this.loaded) return; // the viewport class provides helpful functions for // interacting with the currently-visible area of the // game const viewport = ui.getViewport(); const planets = ui.getPlanetsInViewport(); for (const p of planets) { // use the Viewport class to determine the pixel // coordinates of the planet on the screen const pixelCenter = viewport.worldToCanvasCoords( // @ts-ignore p.location.coords ); // how many pixels is the radius of the planet? const trueRadius = viewport.worldToCanvasDist(ui.getRadiusOfPlanetLevel(p.planetLevel)); // draw nicolas cage on top of the planet ctx.drawImage( this.img, 50, 50, 400, 400, pixelCenter.x - trueRadius, pixelCenter.y - trueRadius, trueRadius * 2, trueRadius * 2 ); } } } export default RageCage; ================================================ FILE: embedded_plugins/Remote-Explorer.ts ================================================ // organize-imports-ignore import type { Chunk, WorldCoords } from '@darkforest_eth/types'; //@ts-ignore import { locationIdFromDecStr } from 'https://cdn.skypack.dev/@darkforest_eth/serde'; import { html, render, useEffect, useState, //@ts-ignore } from 'https://unpkg.com/htm/preact/standalone.module.js'; import type MinerManager from '../src/Backend/Miner/MinerManager'; import type { MinerWorkerMessage } from '../src/_types/global/GlobalTypes'; type ExtendedMinerManager = MinerManager & { url: string; id: number; chunkSize: number; patternType: string; }; const { MinerManager: Miner, SwissCheesePattern, SpiralPattern, TowardsCenterPattern, TowardsCenterPatternV2, } = df.getConstructors(); const NEW_CHUNK = 'DiscoveredNewChunk'; function getPattern(coords: WorldCoords, patternType: string, chunkSize: number) { if (patternType === 'swiss') { return new SwissCheesePattern(coords, chunkSize); } else if (patternType === 'spiral') { return new SpiralPattern(coords, chunkSize); } else if (patternType === 'towardsCenter') { return new TowardsCenterPattern(coords, chunkSize); } else { return new TowardsCenterPatternV2(coords, chunkSize); } } class RemoteWorker implements Worker { private url: string; constructor(url: string) { this.url = url; } async postMessage(msg: string) { const msgJson: MinerWorkerMessage = JSON.parse(msg); const resp = await fetch(this.url, { method: 'POST', body: JSON.stringify({ chunkFootprint: msgJson.chunkFootprint, planetRarity: msgJson.planetRarity, planetHashKey: msgJson.planetHashKey, }), headers: { 'Content-Type': 'application/json', }, }); const exploredChunk = await resp.json(); const chunkCenter = { x: exploredChunk.chunkFootprint.bottomLeft.x + exploredChunk.chunkFootprint.sideLength / 2, y: exploredChunk.chunkFootprint.bottomLeft.y + exploredChunk.chunkFootprint.sideLength / 2, }; exploredChunk.perlin = df.spaceTypePerlin(chunkCenter, false); for (const planetLoc of exploredChunk.planetLocations) { planetLoc.hash = locationIdFromDecStr(planetLoc.hash); planetLoc.perlin = df.spaceTypePerlin({ x: planetLoc.coords.x, y: planetLoc.coords.y }, true); planetLoc.biomebase = df.biomebasePerlin( { x: planetLoc.coords.x, y: planetLoc.coords.y }, true ); } this.onmessage({ data: JSON.stringify([exploredChunk, msgJson.jobId]) }); } onmessage(_a: { data: string }) { console.warn('Unimplemented: onmessage'); } terminate() { console.warn('Unimplemented: terminate'); } onmessageerror() { console.warn('Unimplemented: onmessageerror'); } addEventListener() { console.warn('Unimplemented: addEventListener'); } removeEventListener() { console.warn('Unimplemented: removeEventListener'); } dispatchEvent(_event: Event): boolean { return false; } onerror() { console.warn('Unimplemented: onerror'); } } function Target() { const wrapper = { width: '1em', height: '1em', display: 'inline-block', position: 'relative', verticalAlign: 'text-bottom', }; const svg = { width: '100%', height: '100%', }; const path = { fill: 'white', }; return html` `; } function MinerUI({ miner, onRemove, }: { miner: ExtendedMinerManager; onRemove: (miner: ExtendedMinerManager) => void; }) { const [hashRate, setHashRate] = useState(0); useEffect(() => { const calcHash = (chunk: Chunk, miningTimeMillis: number) => { df.addNewChunk(chunk); const hashRate = chunk.chunkFootprint.sideLength ** 2 / (miningTimeMillis / 1000); setHashRate(Math.floor(hashRate)); const res = miner.getCurrentlyExploringChunk(); if (res) { const { bottomLeft, sideLength } = res; ui?.setExtraMinerLocation?.(miner.id, { x: bottomLeft.x + sideLength / 2, y: bottomLeft.y + sideLength / 2, }); } else { ui?.removeExtraMinerLocation?.(miner.id); } }; miner.on(NEW_CHUNK, calcHash); return () => { miner.off(NEW_CHUNK, calcHash); }; }, [miner]); const wrapper = { paddingBottom: '10px', display: 'flex', justifyContent: 'space-between', whiteSpace: 'nowrap', }; const buttonWrapper = { width: '50px', display: 'flex', justifyContent: 'space-between', }; const remove = () => { onRemove(miner); }; const [targeting, setTargeting] = useState(false); const target = () => setTargeting(true); useEffect(() => { const hover = () => { const coords = ui.getHoveringOverCoords(); if (coords) { ui?.setExtraMinerLocation?.(miner.id, coords); } }; const click = () => { window.removeEventListener('mousemove', hover); window.removeEventListener('click', click); const coords = ui.getHoveringOverCoords(); if (coords) { const pattern = getPattern(coords, miner.patternType, miner.chunkSize); miner.setMiningPattern(pattern); } miner.startExplore(); setTargeting(false); }; if (targeting) { miner.stopExplore(); window.addEventListener('mousemove', hover); window.addEventListener('click', click); } return () => { window.removeEventListener('mousemove', hover); window.removeEventListener('click', click); }; }, [targeting, miner]); return html`
${miner.url} - ${hashRate} hashes/sec
`; } function App({ initialMiners = [], addMiner, removeMiner, }: { initialMiners: ExtendedMinerManager[]; addMiner: (url: string, patternType: string) => ExtendedMinerManager[]; removeMiner: (miner: ExtendedMinerManager) => ExtendedMinerManager[]; }) { const wrapper = { display: 'flex' }; const input = { flex: '1', padding: '5px', outline: 'none', color: 'black', }; const button = { marginLeft: '5px', outline: 'none', }; const select = { background: 'rgb(8,8,8)', }; const [miners, setMiners] = useState(initialMiners); const [nextUrl, setNextUrl] = useState(null); const [patternType, setPatternType] = useState('spiral'); const onChange = (evt: InputEvent) => { setNextUrl((evt.target as HTMLInputElement).value); }; const add = () => { if (nextUrl) { const miners = addMiner(nextUrl, patternType); setMiners(miners); setNextUrl(null); } }; const remove = (miner: ExtendedMinerManager) => { const miners = removeMiner(miner); setMiners(miners); }; const changePattern = (evt: InputEvent) => { setPatternType((evt.target as HTMLSelectElement).value); }; return html`
${miners.map( (miner: ExtendedMinerManager) => html` <${MinerUI} key=${miner.url} miner=${miner} onRemove=${remove} /> ` )}
Explore!
`; } class RemoteExplorerPlugin implements DFPlugin { private miners: ExtendedMinerManager[]; private id: number; constructor() { this.miners = []; this.id = 0; this.addMiner('http://0.0.0.0:8000/mine', 'spiral', 256); } addMiner = (url: string, patternType = 'spiral', chunkSize = 256) => { // TODO: Somehow set a default coords const pattern = getPattern({ x: 0, y: 0 }, patternType, chunkSize); const miner = Miner.create( df.getChunkStore(), pattern, df.getWorldRadius(), df.planetRarity, df.getHashConfig(), false, () => new RemoteWorker(url) ) as ExtendedMinerManager; miner.url = url; miner.id = this.id++; miner.chunkSize = chunkSize; miner.patternType = patternType; miner.startExplore(); this.miners.push(miner); return this.miners; }; removeMiner = (miner: ExtendedMinerManager) => { this.miners = this.miners.filter((m) => { if (m === miner) { ui?.removeExtraMinerLocation?.(m.id); m.stopExplore(); m.destroy(); return false; } else { return true; } }); return this.miners; }; async render(container: HTMLDivElement) { container.style.minWidth = '450px'; container.style.width = 'auto'; render( html` <${App} initialMiners=${this.miners} addMiner=${this.addMiner} removeMiner=${this.removeMiner} /> `, container ); } destroy() { for (const miner of this.miners) { ui?.removeExtraMinerLocation?.(miner.id); miner.stopExplore(); miner.destroy(); } } } export default RemoteExplorerPlugin; ================================================ FILE: embedded_plugins/Renderer-Showcase.ts ================================================ /* eslint-disable */ /** * Below is a list of class definitions for renderers. * These are blank renderers as they have no functionality. * The result of using these renderers is the same as disabling the renderer. */ import { engineConsts, EngineUtils, GameGLManager, GenericRenderer, glsl, //@ts-ignore } from 'https://cdn.skypack.dev/@darkforest_eth/renderer'; import { AsteroidRendererType, AttribType, BackgroundRendererType, BeltRendererType, BlackDomainRendererType, CaptureZoneRendererType, CanvasCoords, Chunk, CircleRendererType, GameViewport, LineRendererType, LocatablePlanet, LocationId, MineBodyRendererType, MineRendererType, PerlinRendererType, Planet, PlanetRendererType, PlanetRenderInfo, PlanetRenderManagerType, QuasarBodyRendererType, QuasarRayRendererType, QuasarRendererType, RectRendererType, RenderedArtifact, RendererType, RGBAVec, RGBVec, RingRendererType, RuinsRendererType, SpaceRendererType, SpacetimeRipRendererType, SpriteRendererType, TextAlign, TextAnchor, TextRendererType, UIRendererType, UniformType, UnminedRendererType, VoyageRendererType, WorldCoords, WormholeRendererType, //@ts-ignore } from 'https://cdn.skypack.dev/@darkforest_eth/types'; //@ts-ignore import { html, render } from 'https://unpkg.com/htm/preact/standalone.module.js'; // Line 78 - 350: Blank Renderer // Line 350 - 651: Circle Renderer // Line 626 - End: Plugin // Line 78 - 376 // "Blank" renderer class definitions // When passing in these renderers into the Dark Forest API, the result would be the same as disabling that type of renderer. class PlanetRenderer implements PlanetRendererType { rendererType = RendererType.Planet; queuePlanetBody(planet: Planet, centerW: WorldCoords, radiusW: number): void {} flush(): void {} } class MineRenderer implements MineRendererType { rendererType = RendererType.Mine; queueMine(planet: Planet, centerW: WorldCoords, radiusW: number): void {} flush(): void {} } class SpacetimeRipRenderer implements SpacetimeRipRendererType { rendererType = RendererType.SpacetimeRip; queueRip(planet: Planet, centerW: WorldCoords, radiusW: number): void {} flush(): void {} } class QuasarRenderer implements QuasarRendererType { rendererType = RendererType.Quasar; queueQuasar(planet: Planet, centerW: WorldCoords, radiusW: number): void {} flush(): void {} } class RuinsRenderer implements RuinsRendererType { rendererType = RendererType.Ruins; queueRuins(planet: Planet, centerW: WorldCoords, radiusW: number): void {} flush(): void {} } class AsteroidRenderer implements AsteroidRendererType { rendererType = RendererType.Asteroid; queueAsteroid(planet: Planet, centerW: CanvasCoords, radiusW: number, color?: RGBVec): void {} flush(): void {} } class RingRenderer implements RingRendererType { rendererType = RendererType.Ring; queueRingAtIdx( planet: Planet, centerW: WorldCoords, radiusW: number, color?: RGBVec, beltIdx?: number, angle?: number ): void {} flush(): void {} } class SpriteRenderer implements SpriteRendererType { rendererType = RendererType.Sprite; //drawing artifacts around world queueArtifactWorld( artifact: RenderedArtifact, posW: CanvasCoords, widthW: number, alpha?: number, atFrame?: number | undefined, color?: RGBVec | undefined, theta?: number | undefined, viewport?: GameViewport ): void {} //drawing artifacts when traveling with voyagers queueArtifact( artifact: RenderedArtifact, pos: CanvasCoords, width?: number, alpha?: number, atFrame?: number | undefined, color?: RGBVec | undefined, theta?: number | undefined ): void {} flush(): void {} } class BlackDomainRenderer implements BlackDomainRendererType { rendererType = RendererType.BlackDomain; queueBlackDomain(planet: Planet, centerW: WorldCoords, radiusW: number): void {} flush(): void {} } class TextRenderer implements TextRendererType { rendererType = RendererType.Text; queueTextWorld( text: string, coords: WorldCoords, color?: RGBAVec, offY?: number, // measured in text units - constant screen-coord offset that it useful for drawing nice things align?: TextAlign, anchor?: TextAnchor, zIdx?: number ): void {} flush(): void {} } class VoyageRenderer implements VoyageRendererType { rendererType = RendererType.Voyager; queueVoyages(): void {} flush(): void {} } class WormholeRenderer implements WormholeRendererType { rendererType = RendererType.Wormhole; queueWormholes(): void {} flush(): void {} } class MineBodyRenderer implements MineBodyRendererType { rendererType = RendererType.MineBody; queueMineScreen(planet: Planet, center: WorldCoords, radius: number, z: number): void {} flush(): void {} setUniforms(): void {} } class BeltRenderer implements BeltRendererType { rendererType = RendererType.Belt; queueBeltAtIdx( planet: Planet, center: WorldCoords | CanvasCoords, radius?: number, color?: RGBVec, beltIdx?: number, angle?: number, screen?: boolean ): void {} flush(): void {} setUniforms(): void {} } class BackgroundRenderer implements BackgroundRendererType { rendererType = RendererType.Background; queueChunks( exploredChunks: Iterable, highPerfMode: boolean, drawChunkBorders: boolean, disableFancySpaceEffect: boolean, innerNebulaColor?: string, nebulaColor?: string, spaceColor?: string, deepSpaceColor?: string, deadSpaceColor?: string ): void {} flush(): void {} } class SpaceRenderer implements SpaceRendererType { rendererType = RendererType.Space; queueChunk(chunk: Chunk): void {} setColorConfiguration( innerNebulaColor: string, nebulaColor: string, spaceColor: string, deepSpaceColor: string, deadSpaceColor: string ): void {} flush(): void {} } class UnminedRenderer implements UnminedRendererType { rendererType = RendererType.Unmined; queueRect( { x, y }: CanvasCoords, width: number, height: number, color: RGBVec, zIdx: number ): void {} flush(): void {} } class PerlinRenderer implements PerlinRendererType { rendererType = RendererType.Perlin; queueChunk(chunk: Chunk): void {} flush(): void {} } class LineRenderer implements LineRendererType { rendererType = RendererType.Line; queueLineWorld( start: WorldCoords, end: WorldCoords, color?: RGBAVec, width?: number, zIdx?: number, dashed?: boolean ): void {} flush(): void {} } class RectRenderer implements RectRendererType { rendererType = RendererType.Rect; queueRectCenterWorld( center: WorldCoords, width: number, height: number, color?: RGBVec, stroke?: number, zIdx?: number ): void {} flush(): void {} } class CircleRenderer implements CircleRendererType { rendererType = RendererType.Circle; queueCircleWorld( center: CanvasCoords, radius: number, color?: RGBAVec, stroke?: number, angle?: number, // percent of arc to render dashed?: boolean ): void {} queueCircleWorldCenterOnly( center: WorldCoords, radius: number, // canvas coords color?: RGBAVec ): void {} flush(): void {} } class UIRenderer implements UIRendererType { rendererType = RendererType.UI; queueBorders(): void {} queueSelectedRangeRing(): void {} queueSelectedRect(): void {} queueHoveringRect(): void {} queueMousePath(): void {} drawMiner(): void {} flush(): void {} } class PlanetRenderManager implements PlanetRenderManagerType { rendererType = RendererType.PlanetManager; queueRangeRings(planet: LocatablePlanet): void {} queuePlanets( cachedPlanets: Map, now: number, highPerfMode: boolean, disableEmojis: boolean, disableHats: boolean ): void {} flush(): void {} } class QuasarBodyRenderer implements QuasarBodyRendererType { rendererType = RendererType.QuasarBody; queueQuasarBody( planet: Planet, centerW: WorldCoords, radiusW: number, z?: number, angle?: number ): void {} flush(): void {} } class QuasarRayRenderer implements QuasarRayRendererType { rendererType = RendererType.QuasarRay; queueQuasarRay( planet: Planet, centerW: WorldCoords, radiusW: number, z?: number, top?: boolean, angle?: number ): void {} flush(): void {} } class CaptureZoneRenderer implements CaptureZoneRendererType{ rendererType = RendererType.CaptureZone; queueCaptureZones(): void {} flush(): void {} } // line 350 - 351 // Circle Renderer Definitions using WebGl // Program Definition // A program is what we use to organizie the attributes and shaders of WebGl Programs const u = { matrix: 'u_matrix', // matrix to convert from world coords to clipspace }; const a = { position: 'a_position', // as [posx, posy, rectposx, rectposy] color: 'a_color', props: 'a_props', // as [stroke, angle, dash] eps: 'a_eps', planetInfo: 'a_planetInfo', //as [planetlevel, radius] PlanetUpgrades: 'a_planetUpgrades', //as [defense:number , range: number, speed: number] PlanetResources: 'a_planetResources', //as [energy:number , energy cap: number, silver: number, silver cap: number] }; const GENERIC_PLANET_PROGRAM_DEFINITION = { uniforms: { matrix: { name: u.matrix, type: UniformType.Mat4 }, }, attribs: { position: { dim: 4, type: AttribType.Float, normalize: false, name: a.position, }, eps: { dim: 1, type: AttribType.Float, normalize: false, name: a.eps, }, color: { dim: 4, type: AttribType.UByte, normalize: true, name: a.color, }, props: { dim: 2, type: AttribType.Float, normalize: false, name: a.props, }, planetInfo: { dim: 2, type: AttribType.UByte, normalize: false, name: a.planetInfo, }, planetUpgrades: { dim: 3, type: AttribType.UByte, normalize: false, name: a.PlanetUpgrades, }, planetResources: { dim: 4, type: AttribType.UByte, normalize: false, name: a.PlanetResources, }, }, vertexShader: glsl` in vec4 a_position; in vec4 a_color; in vec2 a_props; in float a_eps; in vec2 a_planetInfo; in vec4 a_planetResources; uniform mat4 u_matrix; out float v_planetLevel; out vec4 v_color; out vec2 v_rectPos; out float v_angle; out float v_dash; out float v_eps; out float energy; out float energy_cap; void main() { gl_Position = u_matrix * vec4(a_position.xy, 0.0, 1.0); v_rectPos = a_position.zw; v_color = a_color; v_angle = a_props.x; v_dash = a_props.y; v_eps = a_eps; v_planetLevel = a_planetInfo[0]; energy = a_planetResources[0]; energy_cap = a_planetResources[1]; } `, fragmentShader: glsl` #define PI 3.1415926535 precision highp float; out vec4 outColor; in vec4 v_color; in vec2 v_rectPos; in float v_angle; in float v_dash; in float v_eps; in float v_planetLevel; in float energy; in float energy_cap; void main() { vec4 color = v_color; float dist = length(v_rectPos); if (dist > 1.0) discard; // if it's outside the circle // anti-aliasing if barely in the circle float ratio = (1.0 - dist) / v_eps; if (ratio < 1.) { color.a *= ratio; } /* get angle for both angle + dash checks */ float angle = atan(v_rectPos.y, v_rectPos.x); // add 5pi/2 to translate it to [-PI/2, 3PI / 2] float check = angle + (5.0 * PI / 2.0); check -= (check > 2.0 * PI ? 2.0 * PI : 0.0); float pct = check / (2.0 * PI); /* do angle check */ if (v_angle != 1.0 && pct > v_angle) discard; /* do dash check */ bool isDash = v_dash > 0.0; float interval = angle / v_dash; float modulo = interval - 2.0 * floor(interval / 2.0); bool isGap = modulo > 1.0; if (isDash && isGap) discard; /* now draw it */ outColor = vec4(1,1.0/energy_cap*energy,0,1); } `, }; class CirclePlanetRenderer extends GenericRenderer< typeof GENERIC_PLANET_PROGRAM_DEFINITION, GameGLManager > { quadBuffer: number[]; viewport: GameViewport; rendererType: number; vertexShader: string; fragmentShader: string; manager: GameGLManager; constructor(glManager: GameGLManager, n: number) { super(glManager, GENERIC_PLANET_PROGRAM_DEFINITION); //@ts-ignore this.verts = 0; //found in generic renderer this.manager = glManager; const { vertexShader: vert, fragmentShader: frag } = GENERIC_PLANET_PROGRAM_DEFINITION; this.vertexShader = vert; this.fragmentShader = frag; this.rendererType = n; this.viewport = this.manager.renderer.getViewport(); this.quadBuffer = EngineUtils.makeEmptyDoubleQuad(); } public queuePlanet( center: CanvasCoords, radius: number, planet: Planet, angle = 1, // percent of arc to render dashed = false ): void { const color = [255, 255, 255, 255] as RGBAVec; const { position: posA, color: colorA, props: propsA, eps: epsA, planetInfo: planetInfoA, planetUpgrades: planetUpgradesA, planetResources: planetResourcesA, //@ts-ignore } = this.attribManagers; const { x, y } = center; // 1 on either side for antialiasing const r = radius + 1; const { x1, y1 } = { x1: x - r, y1: y - r }; const { x2, y2 } = { x2: x + r, y2: y + r }; // prettier-ignore EngineUtils.makeDoubleQuadBuffered( this.quadBuffer, x1, y1, x2, y2, -1, -1, 1, 1 ); //@ts-ignore posA.setVertex(this.quadBuffer, this.verts); // convert pixels to radians const interval = engineConsts.dashLength; const pixPerRad = radius; const dashRad = interval / pixPerRad; const dash = dashed ? dashRad : -1; const eps = 1 / radius; const resources = [planet.energy, planet.energyCap, planet.silver, planet.silverCap]; for (let i = 0; i < 6; i++) { //@ts-ignore colorA.setVertex(color, this.verts + i); //@ts-ignore propsA.setVertex([angle, dash], this.verts + i); //@ts-ignore planetInfoA.setVertex([planet.planetLevel, radius], this.verts + i); //@ts-ignore planetUpgradesA.setVertex(planet.upgradeState, this.verts + i); //@ts-ignore planetResourcesA.setVertex(resources, this.verts + i); //@ts-ignore epsA.setVertex([eps], this.verts + i); } //@ts-ignore this.verts += 6; } public queueGenericPlanet( planet: Planet, center: WorldCoords, radius: number, // world coords stroke = -1, angle = 1, dashed = false ) { const centerCanvas = this.viewport.worldToCanvasCoords(center); const rCanvas = this.viewport.worldToCanvasDist(radius); this.queuePlanet(centerCanvas, rCanvas, planet, angle, dashed); } public setUniforms() { //@ts-ignore this.uniformSetters.matrix(this.manager.projectionMatrix); } public queuePlanetBody(planet: Planet, centerW: WorldCoords, radiusW: number): void { this.queueGenericPlanet(planet, centerW, radiusW); } public queueMine(planet: Planet, centerW: WorldCoords, radiusW: number): void { this.queueGenericPlanet(planet, centerW, radiusW); } public queueRip(planet: Planet, centerW: WorldCoords, radiusW: number): void { this.queueGenericPlanet(planet, centerW, radiusW); } public queueQuasar(planet: Planet, centerW: WorldCoords, radiusW: number): void { this.queueGenericPlanet(planet, centerW, radiusW); } public queueRuins(planet: Planet, centerW: WorldCoords, radiusW: number): void { this.queueGenericPlanet(planet, centerW, radiusW); } public queueAsteroid(planet: Planet, centerW: CanvasCoords, radiusW: number): void { this.queueGenericPlanet(planet, centerW, radiusW); } } /** * 626-END: Plugin */ export default class EmbeddedRendererShowcase implements DFPlugin { CirclePlanetLibrary: { [key: string]: any }; circleSelector: string; circleChecker: { [key: string]: boolean }; constructor() { let glMan = ui.getGlManager(); this.circleSelector = 'Planet'; if (glMan) { this.CirclePlanetLibrary = { Planet: new CirclePlanetRenderer(glMan, RendererType.Planet), Mine: new CirclePlanetRenderer(glMan, RendererType.Mine), SpacetimeRip: new CirclePlanetRenderer(glMan, RendererType.SpacetimeRip), Quasar: new CirclePlanetRenderer(glMan, RendererType.Quasar), Ruins: new CirclePlanetRenderer(glMan, RendererType.Ruins), }; } this.circleChecker = {}; for (let key in this.CirclePlanetLibrary) { this.circleChecker[key] = false; } } async render(div: HTMLDivElement) { div.style.width = '500px'; render( html`
Disabling Renderers
Whats actually happening here is that the current renderer is being replaced with a 'blank' renderer. A blank renderer is a renderer who's flush function has no functionality. So the behavior is similar to disabling the current renderer.
{ disable(); }} > Disable
Circle Planets
This replaces the current planet renderer with a renderer that uses WebGL to create a circle where the color of the circle changes on the percentage of energy on the planet.
{ const btn = document.getElementById('CirclePlanetBtn'); if (this.circleChecker[this.circleSelector] === false) { ui.setCustomRenderer(this.CirclePlanetLibrary[this.circleSelector]); this.circleChecker[this.circleSelector] = true; btn!.innerText = 'Disable'; } else { ui.disableCustomRenderer(this.CirclePlanetLibrary[this.circleSelector]); this.circleChecker[this.circleSelector] = false; btn!.innerText = 'Enable'; } }} > Enable
Renderer Descriptions
`, div ); } destroy(): void { currentPlanet = 'Planet'; for (let key in rendererLibrary) { ui.disableCustomRenderer(rendererLibrary[key]); } for (let key in this.CirclePlanetLibrary) { ui.disableCustomRenderer(this.CirclePlanetLibrary[key]); } } } let selectStyle = { outline: 'none', background: '#151515', color: '#838383', borderRadius: '4px', border: '1px solid #777', width: '100px', padding: '2px 6px', cursor: 'pointer', margin: '10px', }; let inputStyle = { background: 'rgb(8,8,8)', width: '400px', padding: '3px 5px', }; let buttonStyle = { height: '25px', padding: '3px 5px', margin: '10px', 'text-align': 'center' }; let rendererLibrary: { [key: string]: any } = { Planet: new PlanetRenderer(), Mine: new MineRenderer(), SpacetimeRip: new SpacetimeRipRenderer(), Ruins: new RuinsRenderer(), Quasar: new QuasarRenderer(), Asteroid: new AsteroidRenderer(), MineBody: new MineBodyRenderer(), MineBelt: new BeltRenderer(), Background: new BackgroundRenderer(), UnminedBackground: new UnminedRenderer(), SpaceBackground: new SpaceRenderer(), Artifact: new SpriteRenderer(), AllPlanets: new PlanetRenderManager(), Text: new TextRenderer(), Voyager: new VoyageRenderer(), Perlin: new PerlinRenderer(), Wormhole: new WormholeRenderer(), BlackDomain: new BlackDomainRenderer(), Rectangles: new RectRenderer(), Line: new LineRenderer(), Circle: new CircleRenderer(), Ring: new RingRenderer(), UI: new UIRenderer(), QuasarBody: new QuasarBodyRenderer(), QuasarRay: new QuasarRayRenderer(), CaptureZone: new CaptureZoneRenderer(), }; let disabled: { [key: string]: boolean } = {}; for (let key in rendererLibrary) { disabled[key] = false; } let currentPlanet: string = 'Planet'; function disable() { const btn = document.getElementById('RegPlanetBtn'); if (disabled[currentPlanet] === false) { ui.setCustomRenderer(rendererLibrary[currentPlanet]); disabled[currentPlanet] = true; btn!.innerText = 'Enable'; } else { ui.disableCustomRenderer(rendererLibrary[currentPlanet]); disabled[currentPlanet] = false; btn!.innerText = 'Disable'; } } const cellStyle = { width: '25%', float: 'left', }; const discriptionStyle = { TextAlign: 'justify', }; // Used for discription of each type of renderer let rendererDescription: { [key: string]: any } = { Blank: '', Planet: 'basic planets', Mine: 'asteroid fields', SpacetimeRip: 'Spacetime Rips', Ruins: 'foundries', Quasar: 'quasars', Asteroid: 'asteroid that hover around different planets', MineBody: 'the body/asteroids of asteroid fields', MineBelt: 'the belts/rings around asteroid fields', Background: 'the background of the game', UnminedBackground: 'unmined space chunks (part of the background)', SpaceBackground: 'mined space chunks (part of the background)', Artifact: 'artifacts', AllPlanets: 'All planet types', Text: 'text that are displayed on the game canvas', Voyager: 'Voyages. The transfer of energy between planets.', Perlin: 'the game background. Perlin is the method in which we generate the location of space biomes.', Wormhole: 'visual effects of wormholes. Wormholes are generated by a special type of artifact.', BlackDomain: 'visual effects of black domains. Black Domains are created by a special type of artifact', Rectangles: 'all rectangles drawn in game: indicators for selection of planet, ', Line: ' all lines drawn in game: The line that connect planets during voyages and wormholes ', Circle: 'all circles drawn in the game: The voyager(circle) in voyages, Circles used to indicate the range a planets has, The boarder of the game world. ', Ring: 'rings that indicate the level of a planet', UI: 'in game user interface: game borders, range indicators, selection indicators, mouse path, miner', QuasarBody: 'the body of the Quasar', QuasarRay: 'the ray of the Quasar', CaptureZone: 'the capture zones' }; /* eslint-enable */ ================================================ FILE: index.html ================================================ Dark Forest
================================================ FILE: last_updated.txt ================================================ last updated: Mon Apr 11 18:20:58 UTC 2022 ================================================ FILE: netlify.toml ================================================ [build] command = "yarn build" functions = "functions" publish = "dist" [[redirects]] from = "/*" to = "/index.html" status = 200 [[redirects]] from = "/archive/8d8a7d2a/*" to = "https://6247dc3ca1de3b6ce0c8e184--df-prod.netlify.app/:splat" status = 200 ## (optional) Settings for Netlify Dev ## https://github.com/netlify/cli/blob/master/docs/netlify-dev.md#project-detection #[dev] # command = "yarn start" # Command to start your dev server # port = 3000 # Port that the dev server will be listening on # publish = "dist" # Folder with the static content for _redirect file ## more info on configuring this file: https://www.netlify.com/docs/netlify-toml-reference/ ================================================ FILE: package.json ================================================ { "name": "client", "version": "6.7.29", "private": true, "license": "GPL-3.0", "author": "0xPARC ", "dependencies": { "@darkforest_eth/constants": "6.7.29", "@darkforest_eth/contracts": "6.7.29", "@darkforest_eth/events": "6.7.29", "@darkforest_eth/gamelogic": "6.7.29", "@darkforest_eth/hashing": "6.7.29", "@darkforest_eth/hexgen": "6.7.29", "@darkforest_eth/network": "6.7.29", "@darkforest_eth/procedural": "6.7.29", "@darkforest_eth/renderer": "6.7.29", "@darkforest_eth/serde": "6.7.29", "@darkforest_eth/settings": "6.7.29", "@darkforest_eth/snarks": "6.7.29", "@darkforest_eth/types": "6.7.29", "@darkforest_eth/ui": "6.7.29", "@darkforest_eth/whitelist": "6.7.29", "@lit-labs/react": "^1.0.0", "animejs": "^3.2.1", "auto-bind": "^4.0.0", "bad-words": "^3.0.4", "big-integer": "^1.6.48", "canvas-confetti": "^1.4.0", "color": "^3.0.2", "delay": "^5.0.0", "email-validator": "^2.0.4", "emoji-picker-react": "^3.4.8", "ethers": "^5.5.1", "events": "^3.0.0", "fastq": "^1.10.0", "file-saver": "^2.0.5", "gl-matrix": "^3.3.0", "htm": "^3.0.4", "idb": "^5.0.1", "js-quadtree": "^3.3.5", "json-stable-stringify": "^1.0.1", "jszip": "^3.5.0", "lodash": "^4.17.15", "mnemonist": "^0.38.1", "p-defer": "^3.0.0", "p-timeout": "^4.0.0", "preact": "^10.5.13", "prismjs": "^1.22.0", "react": "^17.0.2", "react-dom": "^17.0.2", "react-loader-spinner": "^4.0.0", "react-router-dom": "^5.3.0", "react-simple-code-editor": "^0.11.0", "react-sortablejs": "^6.0.0", "react-timeago": "^6.2.1", "sortablejs": "^1.10.2", "styled-components": "^5.3.3", "ts-dedent": "^2.0.0", "uuid": "^8.3.2" }, "scripts": { "declarations": "tsc -p tsconfig.decs.json", "test": "exit 0", "lint": "eslint .", "format": "prettier --write .", "start": "webpack-dev-server --mode development --hot", "build": "webpack --mode production", "clean": "del-cli dist node_modules declarations public/contracts tsconfig.ref.tsbuildinfo", "docs": "typedoc && yarn format", "deploy": "netlify build && netlify deploy", "deploy:prod": "netlify build && netlify deploy --prod" }, "browserslist": { "production": [ "last 1 chrome version", "last 1 firefox version" ], "development": [ "last 1 chrome version", "last 1 firefox version" ] }, "engines": { "node": ">=16" }, "devDependencies": { "@babel/core": "^7.17.5", "@babel/preset-env": "^7.16.11", "@babel/preset-react": "^7.16.7", "@babel/preset-typescript": "^7.16.7", "@types/animejs": "^3.1.3", "@types/color": "^3.0.2", "@types/gl-matrix": "^3.2.0", "@types/json-stable-stringify": "^1.0.32", "@types/lodash": "^4.14.160", "@types/prismjs": "^1.16.2", "@types/react": "^17.0.34", "@types/react-dom": "^17.0.11", "@types/react-router-dom": "^5.3.2", "@types/react-timeago": "^4.1.3", "@types/sortablejs": "^1.10.6", "@types/styled-components": "^5.1.15", "@types/uuid": "^8.3.0", "@types/webpack-env": "^1.16.3", "babel-loader": "^8.2.3", "babel-plugin-styled-components": "^2.0.5", "copy-webpack-plugin": "^9.0.1", "css-loader": "^6.5.1", "del-cli": "^4.0.1", "dotenv": "^10.0.0", "eslint": "^7.30.0", "fork-ts-checker-webpack-plugin": "^7.2.1", "html-webpack-plugin": "^5.5.0", "netlify-cli": "^3.8.5", "prettier": "^2.3.0", "raw-loader": "^4.0.2", "resolve-package-path": "^4.0.3", "source-map-loader": "^3.0.0", "style-loader": "^3.3.1", "typedoc": "^0.22.8", "typedoc-plugin-markdown": "3.11.x", "typescript": "4.5.x", "webpack": "^5.62.1", "webpack-bundle-analyzer": "^4.5.0", "webpack-cli": "^4.9.1", "webpack-dev-server": "^4.4.0" } } ================================================ FILE: plugins/PluginTemplate.ts ================================================ /** * Remember, you have access these globals: * 1. df - Just like the df object in your console. * 2. ui - For interacting with the game's user interface. * * Let's log these to the console when you run your plugin! */ console.log(df, ui); class PluginTemplate implements DFPlugin { constructor() {} /** * Called when plugin is launched with the "run" button. */ async render(container: HTMLDivElement) {} /** * Called when plugin modal is closed. */ destroy() {} } /** * And don't forget to export it! */ export default PluginTemplate; ================================================ FILE: plugins/README.md ================================================ # Plugins TypeScript or JavaScript files in this directory will automatically be bundled and served as plugins when running `df-plugin-dev-server` in the root of the game `client`! Check out [Plugin Development](../README.md#plugin-development) in the main README for the steps to set it up. ================================================ FILE: plugins/RendererPlugin.md ================================================ # Pluginnable Renderers This article is about creating custom renderers for Dark Forest through the use of our plugin system. # Background Dark Forest uses WebGL to visualize the game. WebGL is a JavaScript API that is used for rendering high-performance 3D graphics. Only certain browsers support WebGL. Click here to check: https://get.webgl.org/WebGL/. Click here to Learn more about WebGL: https://WebGLfundamentals.org/webgl/lessons/webgl-getting-WebGL.html # What is a Renderer Renderers are classes that are used to draw specific types of entities such as planets or asteroids onto the Dark Forest game canvas. Custom made renderers can be passed into the Dark Forest API to replace the current renderer for that entity type. Renderers have two main methods: - Queue (a method that starts with `queue` followed by the name of the object it's rendering): The game calls the queue method for each instance of an object specified by the renderer type and accepts as an input any information relevant to the object. This information is then added to some implementation of a queue within the renderer for later use in the flush method. - Flush: Flush is called every frame in the game. When called, the entities in the queue should be rendered onto the game canvas and the queue cleared. For the game to recognize if the class is a renderer, it has to follow one of the renderer interfaces that can be found in `@darkforest_eth/types` For instance `MineRendererType` is an interface for mine renderers. Mine renderers are used to draw Asteroid Fields which is a type of planet (not to be confused with Asteroids). ![Asteroid Field](./CircleBefore.png) The `MineRendererType` has 2 abstract methods: - `queueMine` is called by the game to queue an Asteroid Field to be drawn. The method has 3 parameters: - `planet`: A Planet object that contains information about the current Asteroid Field - `centerW`: a set of x and y coordinates that represent the position of the center of the Asteroid Field relative to the game world. - `radiusW`: the size of the Asteroid Field relative to the game world. - `flush` called by the game to draw all queued up Asteroid Field. Here is Dark Forest's current implementation of its mine renderer: https://github.com/darkforest-eth/packages/blob/master/renderer/src/Entities/MineRenderer.ts ## Draw Order ### The Order in Which Renderer Managers and Renderers Are Flushed 1. Background - Unmined - Space 2. Capture Zones 3. Line Renderer 4. Planet Manager - Planet - Asteroid - Mine - Spacetime Rip - Ruins - Ring - Quasar - Black Domain 5. UI 6. Voyager 7. Wormhole 8. Circle 9. Rect 10. Text 11. Sprite (Artifacts) ### When Multiple Renderers Implementing the Same Interface Are Passed Into the API: When you replace a renderer in the game, the renderer is then put on our rendering stack. The rendering stack is used to determine which renderer to use if multiple renderers loaded for the same entity at the same time. The game will use the top most renderer. A renderer at any position in the stack can be popped. # Example Plugin We will run through the creation of a renderer that will replace the renderer for Asteroid Fields. To create a renderer it has to follow one of the renderer interfaces that can be found in ​​`@darkforest_eth/types`. For the Asteroid Fields, we will be using the `MineRendererType`. ```javascript import { RendererType } from 'https://cdn.skypack.dev/@darkforest_eth/types'; class GenericMineRenderer { constructor() { this.rendererType = RendererType.Mine; } queueMine(planet, centerW, radiusW) {} flush() {} } ``` After implementing the interface the code should look similar to the code above. The code above can be considered a fully functional renderer. The resulting behavior would be as if you were disabling the renderer for the Asteroid Renderer. Before: ![Before](./DisableBefore.png) After: ![After](./DisableAfter.png) If you noticed in the queue function, the coordinate of the planet and the size of planet is relative to the game world. However when drawn on the canvas, the size and location of planet change based on the position of the players camera. We provide developers a way to easily transform between coordinate systems via the viewport class. You can get the viewport from the global ui class. `ui.getViewport()`. An example of the use of the viewport can be seen in the code of the `queueMine` function below. ```javascript import { RendererType } from 'https://cdn.skypack.dev/@darkforest_eth/types'; class GenericMineRenderer { constructor() { this.viewport = ui.getViewport(); this.rendererType = RendererType.Mine; } queueMine(planet, centerW, radiusW) { const centerCanvas = this.viewport.worldToCanvasCoords(centerW); const rCanvas = this.viewport.worldToCanvasDist(radiusW); } flush() {} } ``` The `Viewport` class is used to translate between game world and canvas for distances and coordinates: https://github.com/darkforest-eth/packages/blob/master/types/interfaces/GameViewport.md These are the 2 functions used above. - `worldToCanvasCoords` will translate from game world coordinates to canvas coordinates. In the code above we are translating the location of the Asteroid Field to its location relative to the canvas. - `worldToCanvasDist` will translate a distance relative to the game world to the pixel distance on the canvas. After implementing the above code you should be ready to start implementing code that will draw on the game canvas. To do this you will need to be able to access the `WebGLRenderingContext`. ## WebGL Code The next few sections will be about writing the WebGL Code. We will be creating WebGL code that renders a circle instead of the original asteroid field. The color of the asteroid field while change based on the current energy level of the planet. The RGB value of the color is determined by this equation `red: 255 green: 255/(energy cap) \*(current energy) blue: 0`. The goal of this section is to give you a basic understanding of how the WebGL code works. If you want to dive deeper into WebGL check out this website: https://WebGLfundamentals.org/webgl/lessons/webgl-how-it-works.html ## Definitions Clip space: The 3D coordinate system used by WebGL for its canvas. All coordinates range fro`m -1 to 1. Vertex shader: The vertex shader is used to determine the location of a vertex on the WebGL clip space. WebGL will draw a shape onto the canvas based on the passed in vertices. For instance, the triangle draw mode will connect every 3 consecutive vertices to create a triangle. Fragment Shader: Once a shape is created, the fragment shader is used to determine the color of each individual pixel in the shape. Attribute Variables: The attributes are values that are passed in from outside the shader program to the vertex shader. Each vertex has its on set of attribute variables. Uniform Variables: The uniforms are effectively global variables you set before the execution of the program. All vertices share the same uniform variables. Varying Variables: Variables that will be transferred to be used in the fragment shader The code below contains all of the objects that we will explore to demonstrate WebGL rendering. The two shaders are wrapped into a RendererProgram class. The RendererProgram is how the Dark Forest team structures our code. You do not have to follow this structure to create a working WebGL Program. ## Fragment and Vertex Shaders ```javascript import { glsl } from 'https://cdn.skypack.dev/@darkforest_eth/renderer'; import { RendererProgram, AttribeType, UniformType, } from 'https://cdn.skypack.dev/@darkforest_eth/types'; const program = { uniforms: { matrix: { name: 'u_matrix', type: UniformType.Mat4 }, }, attribs: { position: { dim: 4, type: AttribType.Float, normalize: false, name: 'a_position', //the name of the attribute variable }, energyInfo: { dim: 2, type: AttribType.Float, normalize: false, name: 'a_energy', }, }, vertexShader: glsl` in vec4 a_position; in vec2 a_energy; uniform mat4 u_matrix; out vec2 v_rectPos; out float v_energy; out float v_energy_cap; void main() { // converting from canvas coordinates too clip space gl_Position = u_matrix * vec4(a_position.xy, 0.0, 1.0); //setting the varrying variables for use in the fragment shader v_energy = a_energy.x; v_energy_cap = a_energy.y; v_rectPos = a_position.zw; } `, fragmentShader: glsl` #define PI 3.1415926535 precision highp float; out vec4 outColor; in vec2 v_rectPos; in float v_energy; in float v_energy_cap; void main() { float dist = length(v_rectPos); // if it's outside the circle if (dist > 1.0) discard; //determine the color of the pixel using rgb values //[red,green,blue,opacity] the range of the numbers is from 0 to 1 outColor = vec4(1,1.0/v_energy_cap*v_energy,0,1); } `, }; ``` For the both the Vertex and Fragment shader `glsl` is used. `glsl` formats the string to be readable by the shader compiler. ### Vertex Shader `a_position` and `a_energy` are attributes and `u_matrix` is a uniform for this shader program. - `a_position` is a vector that contains the location of the vertex being drawn - `a_energy` is a vector that contains information about the energy of the Asteroid Field. The `u_matrix` is a projection matrix. It is used to do some fancy math in line 10: `gl_Position = u_matrix * vec4(a_position.xy, 0.0, 1.0);` This is transforming coordinates from the canvas into clip space coordinates. If you want to read more about how this math done, checkout this link: https://developer.mozilla.org/en-US/docs/Web/API/WebGL_API/WebGL_model_view_projection. With the combination of some code we will write later, the vertex shader will draw a square at the coordinates we decide. ### Fragment Shader The fragment shader is called for every pixel in the square. The `outColor` is the color of the pixel. This fragment draw a circle with a clip space radius of 1. The fragment shader checks if the pixel is one unit away from the center of the square by use of the length function and `discard`s the pixel away if it is not. ## Renderer Code ```javascript import { EngineUtils, GenericRenderer, glsl } from 'https://cdn.skypack.dev/@darkforest_eth/renderer'; import { AttribType, RendererType, UniformType, } from 'https://cdn.skypack.dev/@darkforest_eth/types'; class GenericMineRenderer extends GenericRenderer { constructor(gl, vp) { super(gl, program); this.rendererType = RendererType.Mine; this.gl = gl; this.viewport = vp; this.quadBuffer = EngineUtils.makeEmptyDoubleQuad(); } ``` The `GameGLManager` is a wrapper class that contains the WebGLRenderingContext: https://github.com/darkforest-eth/packages/blob/master/renderer/classes/GameGLManager.md `GenericRenderer` is a class that was created by the Dark Forest team for the purposes of organization. `GenericRenderer` sets up a lot of the commonly used WebGL code. You do not have to use this to create functioning WebGL Code. You can look at the `GenericRenderer` Code here: https://github.com/darkforest-eth/packages/blob/master/renderer/src/WebGL/GenericRenderer.ts The `quadBuffer` is an array that will contain the vertices of the square being drawn. `EngineUtils` is a tool the Dark Forest team created for commonly used WebGL operations: https://github.com/darkforest-eth/packages/blob/master/renderer/src/EngineUtils.ts ```typescript public flush(drawMode: DrawMode = DrawMode.Triangles) { if (this.verts === 0) return; const { gl } = this.manager; gl.useProgram(this.program); this.setUniforms(); for (const attrib in this.attribManagers) { this.attribManagers[attrib].bufferData(this.verts); } // draw gl.drawArrays(drawMode, 0, this.verts); this.verts = 0; } ``` This is the flush function which was inherited by `GenericRenderer`. The default `DrawMode` is to use triangles. This means whenever 3 vertices are put into the vertex shader it will draw a triangle. As stated above the goal of the vertex shader was to draw a square. To do that with triangle we need to draw 2 triangles connected by one their sides. The result of this will require us to import 6 vertices. Square: ![Square](./square.png) ```javascript queueMine(planet: Planet, centerW: WorldCoords, radiusW: number): void { //converting from game coordinates to canvas coordinates const centerCanvas = this.viewport.worldToCanvasCoords(centerW); const rCanvas = this.viewport.worldToCanvasDist(radiusW); this.queueCircle(planet, centerCanvas, rCanvas); } queueCircle(planet: Planet, center: CanvasCoords, radius: number) { const { position: posA, energyInfo: energyA } = this.attribManagers; const r = radius + 1; const { x, y } = center; //calculating the top left and bottom right of the bounding square. const { x1, y1 } = { x1: x - r, y1: y - r }; const { x2, y2 } = { x2: x + r, y2: y + r }; //creating an array of all the vertices for the triangle 14 EngineUtils.makeDoubleQuadBuffered( this.quadBuffer, x1, y1, x2, y2, -1, -1, 1, 1 ); //passing in the vertices to the vertex shader posA.setVertex(this.quadBuffer, this.verts); //passing in the energy information. 6 times because we passed in 6 vertices. for (let i = 0; i < 6; i++) { energyA.setVertex([planet.energy, planet.energyCap], this.verts + i); } this.verts += 6; } setUniforms() { if (!this.gl) return; this.uniformSetters.matrix(this.gl.projectionMatrix); } ``` The `makeDoubleQuadBuffered` code creates an array of size 24. We are then inserting the array to be imported into fragment shader through `a_position` attribute. Since `a_position` is imported as a `vec4` the `quadbuffer` is split evenly into 6 vectors of size 4, one for each vertex. Each vector is formatted like this: [canvas x, canvas y, clip space x, clip space y] In relation to the square, the coordinates of the 6 vertices are the top left, top right, bottom left, bottom right of the square. Where the top right and bottom left repeat twice. ```javascript for (let i = 0; i < 6; i++) { energyA.setVertex([planet.energy, planet.energyCap], this.verts + i); } this.verts += 6; ``` For each vertex we are importing the information about the planet's energy to the `a_energy` attribute. ## Final Renderer Code The final code for the renderer looks like this: ```javascript import { EngineUtils, GenericRenderer, glsl, } from 'https://cdn.skypack.dev/@darkforest_eth/renderer'; import { AttribType, RendererType, UniformType, } from 'https://cdn.skypack.dev/@darkforest_eth/types'; class GenericMineRenderer extends GenericRenderer { constructor(gl, vp) { super(gl, program); this.rendererType = RendererType.Mine; this.gl = gl; this.viewport = vp; this.quadBuffer = EngineUtils.makeEmptyDoubleQuad(); } queueMine(planet, centerW, radiusW) { //converting from game coordinates to canvas coordinates const centerCanvas = this.viewport.worldToCanvasCoords(centerW); const rCanvas = this.viewport.worldToCanvasDist(radiusW); this.queueCircle(planet, centerCanvas, rCanvas); } queueCircle(planet, center, radius) { const { position: posA, energyInfo: energyA } = this.attribManagers; const r = radius + 1; const { x, y } = center; //calculating the top left and bottom right of the bounding square. const { x1, y1 } = { x1: x - r, y1: y - r }; const { x2, y2 } = { x2: x + r, y2: y + r }; //creating an array of all the vertices for the triangle 14 EngineUtils.makeDoubleQuadBuffered(this.quadBuffer, x1, y1, x2, y2, -1, -1, 1, 1); //passing in the vertices to the vertex shader posA.setVertex(this.quadBuffer, this.verts); //passing in the energy information. 6 times because we passed in 6 vertices. for (let i = 0; i < 6; i++) { energyA.setVertex([planet.energy, planet.energyCap], this.verts + i); } this.verts += 6; } setUniforms() { if (!this.gl) return; this.uniformSetters.matrix(this.gl.projectionMatrix); } } const program = { uniforms: { matrix: { name: 'u_matrix', type: UniformType.Mat4 }, }, attribs: { position: { dim: 4, type: AttribType.Float, normalize: false, name: 'a_position', //the name of the attribute variable }, energyInfo: { dim: 2, type: AttribType.Float, normalize: false, name: 'a_energy', }, }, vertexShader: glsl` in vec4 a_position; in vec2 a_energy; uniform mat4 u_matrix; out vec2 v_rectPos; out float v_energy; out float v_energy_cap; void main() { // converting from canvas coordinates too clip space gl_Position = u_matrix * vec4(a_position.xy, 0.0, 1.0); //setting the varrying variables for use in the fragment shader v_energy = a_energy.x; v_energy_cap = a_energy.y; v_rectPos = a_position.zw; } `, fragmentShader: glsl` #define PI 3.1415926535 precision highp float; out vec4 outColor; in vec2 v_rectPos; in float v_energy; in float v_energy_cap; void main() { float dist = length(v_rectPos); // if it's outside the circle if (dist > 1.0) discard; //determine the color of the pixel using rgb values //[red,green,blue,opacity] the range of the numbers is from 0 to 1 outColor = vec4(1,1.0/v_energy_cap*v_energy,0,1); } `, }; ``` Once we are done implementing the renderer we can call `ui.setCustomRenderer` to start rendering our own Asteroid Field Renderer. ```javascript export default class ExamplePlugin { constructor() { const gl = ui.getGlManager(); if (!gl) return; this.mineRenderer = new GenericMineRenderer(gl, ui.getViewport()); ui.setCustomRenderer(this.mineRenderer); } async render(div) {} destroy() { console.log('destroying renderer plugin'); ui.disableCustomRenderer(this.mineRenderer); } } ``` The code above is an example of implementing the renderer with plugins. When the plugin is started it creates a new instance of our custom Asteroid Field Renderer and then uses `setCustomRenderer` to activate it. When the plugin is destroyed we disable the renderer by using `disableCustomRenderer`. The result of running the plugin can be seen below. Before: ![CirlceBefore](./CircleBefore.png) After: ![CirlceAfter](./CircleAfter.png) # Documentation Links ## Renderer Types All renderer types can be found here: https://github.com/darkforest-eth/packages/tree/master/types#type-declaration-27 To find the interface for each renderer go to the link below. The name of the interface is the entity name followed by `RendererType`: https://github.com/darkforest-eth/packages/tree/master/types#interfaces ## Dark Forest Renderers Below are examples of Dark Forest renderers Basic Planets - Renderer Code: https://github.com/darkforest-eth/packages/blob/master/renderer/src/Entities/PlanetRenderer.ts - WebGL Code: https://github.com/darkforest-eth/packages/blob/master/renderer/src/Programs/PlanetProgram.ts Background Renderer: - Background Renderer is a renderer manager that contains multiple renderer used to draw different sections of the background. - Renderer Code: https://github.com/darkforest-eth/packages/blob/master/renderer/src/Entities/BackgroundRenderer.ts - Unmined Renderer: - Renderer Code: https://github.com/darkforest-eth/packages/blob/master/renderer/src/Entities/UnminedRenderer.ts - WebGL Code: https://github.com/darkforest-eth/packages/blob/master/renderer/src/Programs/UnminedProgram.ts - Space Renderer: - Renderer Code: https://github.com/darkforest-eth/packages/blob/master/renderer/src/Entities/SpaceRenderer.ts - WebGL Code: https://github.com/darkforest-eth/packages/blob/master/renderer/src/Programs/SpaceProgram.ts If you want to look at more renderers, you can find all the Dark Forest renderers here: https://github.com/darkforest-eth/packages/tree/master/renderer/src/Entities The WebGl Code for those renderers can be found here: https://github.com/darkforest-eth/packages/tree/master/renderer/src/Programs ## Rendering Tools Generic Renderer is base class that Dark Forest team created for all of its renderers: https://github.com/darkforest-eth/packages/blob/master/renderer/src/WebGL/GenericRenderer.ts Engine Utils contains function for common WebGl Operations : https://github.com/darkforest-eth/packages/blob/master/renderer/classes/EngineUtils.md https://github.com/darkforest-eth/packages/blob/master/renderer/src/EngineUtils.ts The viewport class is used to translate between game and canvas coordinate systems: https://github.com/darkforest-eth/packages/blob/master/types/interfaces/GameViewport.md Our Wrapper class for the WebGL2RenderingContext GameGlManager: https://github.com/darkforest-eth/packages/blob/master/renderer/classes/GameGLManager.md If you want to mess with canvas 2D rendering you can access a `CanvasRenderingContext2D` by calling `ui.get2dRenderer()` WARNING: all images drawn with the `CanvasRenderingContext2D` will all be on top of WebGl ================================================ FILE: public/manifest.json ================================================ { "name": "Dark Forest", "short_name": "Dark Forest", "background_color": "#080808", "theme_color": "#080808", "icons": [ { "src": "/favicons.ico/android-icon-36x36.png", "sizes": "36x36", "type": "image/png", "density": "0.75" }, { "src": "/favicons.ico/android-icon-48x48.png", "sizes": "48x48", "type": "image/png", "density": "1.0" }, { "src": "/favicons.ico/android-icon-72x72.png", "sizes": "72x72", "type": "image/png", "density": "1.5" }, { "src": "/favicons.ico/android-icon-96x96.png", "sizes": "96x96", "type": "image/png", "density": "2.0" }, { "src": "/favicons.ico/android-icon-144x144.png", "sizes": "144x144", "type": "image/png", "density": "3.0" }, { "src": "/favicons.ico/android-icon-192x192.png", "sizes": "192x192", "type": "image/png", "density": "4.0" }, { "src": "/favicons.ico/favicon-96x96.png", "sizes": "96x96", "type": "image/png", "density": "2.0" } ] } ================================================ FILE: public/robots.txt ================================================ # https://www.robotstxt.org/robotstxt.html User-agent: * ================================================ FILE: src/Backend/GameLogic/ArrivalUtils.ts ================================================ import { CONTRACT_PRECISION } from '@darkforest_eth/constants'; import { hasOwner, isActivated, isEmojiFlagMessage } from '@darkforest_eth/gamelogic'; import { ArrivalType, Artifact, ArtifactType, EmojiFlagBody, Planet, PlanetMessage, PlanetType, QueuedArrival, Upgrade, } from '@darkforest_eth/types'; import _ from 'lodash'; import { ContractConstants } from '../../_types/darkforest/api/ContractsAPITypes'; // TODO: planet class, cmon, let's go export const blocksLeftToProspectExpiration = ( currentBlockNumber: number, prospectedBlockNumber?: number ) => { return (prospectedBlockNumber || 0) + 255 - currentBlockNumber; }; // TODO: Planet. Class. export const prospectExpired = (currentBlockNumber: number, prospectedBlockNumber: number) => { return blocksLeftToProspectExpiration(currentBlockNumber, prospectedBlockNumber) <= 0; }; export const isFindable = (planet: Planet, currentBlockNumber?: number): boolean => { return ( currentBlockNumber !== undefined && planet.planetType === PlanetType.RUINS && planet.prospectedBlockNumber !== undefined && !planet.hasTriedFindingArtifact && !prospectExpired(currentBlockNumber, planet.prospectedBlockNumber) ); }; export const isProspectable = (planet: Planet): boolean => { return planet.planetType === PlanetType.RUINS && planet.prospectedBlockNumber === undefined; }; const getSilverOverTime = ( planet: Planet, startTimeMillis: number, endTimeMillis: number ): number => { if (!hasOwner(planet)) { return planet.silver; } if (planet.silver > planet.silverCap) { return planet.silverCap; } const timeElapsed = endTimeMillis / 1000 - startTimeMillis / 1000; return Math.min(timeElapsed * planet.silverGrowth + planet.silver, planet.silverCap); }; const getEnergyAtTime = (planet: Planet, atTimeMillis: number): number => { if (planet.energy === 0) { return 0; } if (!hasOwner(planet)) { return planet.energy; } if (planet.planetType === PlanetType.SILVER_BANK) { if (planet.energy > planet.energyCap) { return planet.energyCap; } } const timeElapsed = atTimeMillis / 1000 - planet.lastUpdated; const denominator = Math.exp((-4 * planet.energyGrowth * timeElapsed) / planet.energyCap) * (planet.energyCap / planet.energy - 1) + 1; return planet.energyCap / denominator; }; export const updatePlanetToTime = ( planet: Planet, planetArtifacts: Artifact[], atTimeMillis: number, contractConstants: ContractConstants, setPlanet: (p: Planet) => void = () => {} ): void => { if (atTimeMillis < planet.lastUpdated * 1000) { return; } if (planet.pausers === 0) { planet.silver = getSilverOverTime(planet, planet.lastUpdated * 1000, atTimeMillis); planet.energy = getEnergyAtTime(planet, atTimeMillis); } planet.lastUpdated = atTimeMillis / 1000; const photoidActivationTime = contractConstants.PHOTOID_ACTIVATION_DELAY * 1000; const activePhotoid = planetArtifacts.find( (a) => a.artifactType === ArtifactType.PhotoidCannon && isActivated(a) && atTimeMillis - a.lastActivated * 1000 >= photoidActivationTime ); if (activePhotoid && !planet.localPhotoidUpgrade) { planet.localPhotoidUpgrade = activePhotoid.timeDelayedUpgrade; applyUpgrade(planet, activePhotoid.timeDelayedUpgrade); } setPlanet(planet); }; export const applyUpgrade = (planet: Planet, upgrade: Upgrade, unApply = false) => { if (unApply) { planet.speed /= upgrade.energyCapMultiplier / 100; planet.energyGrowth /= upgrade.energyGroMultiplier / 100; planet.range /= upgrade.rangeMultiplier / 100; planet.speed /= upgrade.speedMultiplier / 100; planet.defense /= upgrade.defMultiplier / 100; } else { planet.speed *= upgrade.energyCapMultiplier / 100; planet.energyGrowth *= upgrade.energyGroMultiplier / 100; planet.range *= upgrade.rangeMultiplier / 100; planet.speed *= upgrade.speedMultiplier / 100; planet.defense *= upgrade.defMultiplier / 100; } }; /** * @param previous The previously calculated state of a planet * @param current The current calculated state of the planet * @param arrival The Arrival that caused the state change */ export interface PlanetDiff { previous: Planet; current: Planet; arrival: QueuedArrival; } export const arrive = ( toPlanet: Planet, artifactsOnPlanet: Artifact[], arrival: QueuedArrival, arrivingArtifact: Artifact | undefined, contractConstants: ContractConstants ): PlanetDiff => { // this function optimistically simulates an arrival if (toPlanet.locationId !== arrival.toPlanet) { throw new Error(`attempted to apply arrival for wrong toPlanet ${toPlanet.locationId}`); } // update toPlanet energy and silver right before arrival updatePlanetToTime(toPlanet, artifactsOnPlanet, arrival.arrivalTime * 1000, contractConstants); const prevPlanet = _.cloneDeep(toPlanet); if (toPlanet.destroyed) { return { arrival: arrival, previous: toPlanet, current: toPlanet }; } // apply energy const { energyArriving } = arrival; if (arrival.player !== toPlanet.owner) { if (arrival.arrivalType === ArrivalType.Wormhole) { // if this is a wormhole arrival to a planet that isn't owned by the initiator of // the move, then don't move any energy } // attacking enemy - includes emptyAddress else if ( toPlanet.energy > Math.floor((energyArriving * CONTRACT_PRECISION * 100) / toPlanet.defense) / CONTRACT_PRECISION ) { // attack reduces target planet's garrison but doesn't conquer it toPlanet.energy -= Math.floor((energyArriving * CONTRACT_PRECISION * 100) / toPlanet.defense) / CONTRACT_PRECISION; } else { // conquers planet toPlanet.owner = arrival.player; toPlanet.energy = energyArriving - Math.floor((toPlanet.energy * CONTRACT_PRECISION * toPlanet.defense) / 100) / CONTRACT_PRECISION; } } else { // moving between my own planets toPlanet.energy += energyArriving; } if (toPlanet.planetType === PlanetType.SILVER_BANK || toPlanet.pausers !== 0) { if (toPlanet.energy > toPlanet.energyCap) { toPlanet.energy = toPlanet.energyCap; } } // apply silver if (toPlanet.silver + arrival.silverMoved > toPlanet.silverCap) { toPlanet.silver = toPlanet.silverCap; } else { toPlanet.silver += arrival.silverMoved; } // transfer artifact if necessary if (arrival.artifactId) { toPlanet.heldArtifactIds.push(arrival.artifactId); } if (arrivingArtifact) { if (arrivingArtifact.artifactType === ArtifactType.ShipMothership) { toPlanet.energyGrowth *= 2; } else if (arrivingArtifact.artifactType === ArtifactType.ShipWhale) { toPlanet.silverGrowth *= 2; } else if (arrivingArtifact.artifactType === ArtifactType.ShipTitan) { toPlanet.pausers++; } arrivingArtifact.onPlanetId = toPlanet.locationId; } return { arrival, current: toPlanet, previous: prevPlanet }; }; /** * @todo ArrivalUtils has become a dumping ground for functions that should just live inside of a * `Planet` class. */ export function getEmojiMessage( planet: Planet | undefined ): PlanetMessage | undefined { return planet?.messages?.find(isEmojiFlagMessage); } ================================================ FILE: src/Backend/GameLogic/CaptureZoneGenerator.ts ================================================ import { monomitter, Monomitter } from '@darkforest_eth/events'; import { CaptureZone, Chunk, LocationId } from '@darkforest_eth/types'; import bigInt from 'big-integer'; import { utils } from 'ethers'; import GameManager, { GameManagerEvent } from './GameManager'; export type CaptureZonesGeneratedEvent = { changeBlock: number; nextChangeBlock: number; zones: CaptureZone[]; }; /** * Given a game start block and a zone change block interval, decide when to generate new Capture Zones. */ export class CaptureZoneGenerator { private gameManager: GameManager; private zones: Set; private capturablePlanets: Set; private lastChangeBlock: number; private nextChangeBlock: number; private changeInterval: number; public readonly generated$: Monomitter; constructor(gameManager: GameManager, gameStartBlock: number, changeInterval: number) { this.gameManager = gameManager; this.changeInterval = changeInterval; this.nextChangeBlock = gameStartBlock; this.generated$ = monomitter(); this.capturablePlanets = new Set(); this.zones = new Set(); gameManager.on(GameManagerEvent.DiscoveredNewChunk, this.onNewChunk.bind(this)); } /** * Call when a new block is received to check if generation is needed. * @param blockNumber Current block number. */ async generate(blockNumber: number) { this.setNextGenerationBlock(blockNumber); const newZones = await this._generate(this.nextChangeBlock - this.changeInterval); this.zones = newZones; this.updateCapturablePlanets(); this.generated$.publish({ changeBlock: this.lastChangeBlock, nextChangeBlock: this.nextChangeBlock, zones: Array.from(this.zones), }); } private setNextGenerationBlock(blockNumber: number) { const totalGameBlocks = blockNumber - this.gameManager.getContractConstants().GAME_START_BLOCK; const numPastIntervals = Math.floor(totalGameBlocks / this.changeInterval); this.nextChangeBlock = this.gameManager.getContractConstants().GAME_START_BLOCK + (numPastIntervals + 1) * this.changeInterval; } private async _generate(blockNumber: number) { const block = await this.gameManager.getEthConnection().getProvider().getBlock(blockNumber); const worldRadius = await this.gameManager.getContractAPI().getWorldRadius(); const captureZones = new Set(); const ringSize = 5000; const ringCount = Math.floor(worldRadius / ringSize); const zonesPerRing = this.gameManager.getContractConstants().CAPTURE_ZONES_PER_5000_WORLD_RADIUS; for (let ring = 0; ring < ringCount; ring++) { const nonceBase = ring * zonesPerRing; for (let j = 0; j < zonesPerRing; j++) { const nonce = nonceBase + j; const blockAndNonceHash = utils.solidityKeccak256( ['bytes32', 'uint256'], [block.hash, nonce] ); // Chop off 0x and convert to BigInt const seed = bigInt(blockAndNonceHash.substring(2, blockAndNonceHash.length), 16); // Last 3 hex characters const angleSeed = seed.mod(0xfff); // Max value of 0xfff is 4095 // 4095 / 651 is max radians in circle // Mult by 1e18 to convert to big number math const angleRads = angleSeed.multiply(1e18).divide(651); // Next 6 hex characters const distanceSeed = seed.minus(angleSeed).divide(4096).mod(0xffffff); // 16777215 is value of FFFFFF // Clamp distance within ring radius const divisor = Math.floor(16777215 / ringSize); // Add in distance from origin point const distance = distanceSeed.divide(divisor).add(ring * ringSize); // Bring it back down to number const angleNumber = Number(angleRads) / 1e18; const distanceNumber = Number(distance); const coords = { x: Math.floor(distanceNumber * Math.cos(angleNumber)), y: Math.floor(distanceNumber * Math.sin(angleNumber)), }; captureZones.add({ coords, radius: this.gameManager.getContractConstants().CAPTURE_ZONE_RADIUS, }); } } this.lastChangeBlock = blockNumber; return captureZones; } private updateCapturablePlanets() { this.capturablePlanets = new Set(); for (const zone of this.getZones()) { const planetsInZone = this.gameObjects.getPlanetsInWorldCircle(zone.coords, zone.radius); for (const planet of planetsInZone) { this.capturablePlanets.add(planet.locationId); } } } private get gameObjects() { return this.gameManager.getGameObjects(); } private onNewChunk(chunk: Chunk) { for (const worldLocation of chunk.planetLocations) { for (const zone of this.getZones()) { const { x: planetX, y: planetY } = worldLocation.coords; const { x: zoneX, y: zoneY } = zone.coords; const distance = Math.sqrt((planetX - zoneX) ** 2 + (planetY - zoneY) ** 2); if (distance <= zone.radius) { this.capturablePlanets.add(worldLocation.hash); } } } } /** * Is the given planet inside of a Capture Zone. */ public isInZone(locationId: LocationId) { return this.capturablePlanets.has(locationId); } /** * The next block that will trigger a Capture Zone generation. */ public getNextChangeBlock() { return this.nextChangeBlock; } public getZones() { return this.zones; } } ================================================ FILE: src/Backend/GameLogic/ContractsAPI.ts ================================================ import { EMPTY_LOCATION_ID } from '@darkforest_eth/constants'; import { DarkForest } from '@darkforest_eth/contracts/typechain'; import { aggregateBulkGetter, ContractCaller, EthConnection, ethToWei, TxCollection, TxExecutor, } from '@darkforest_eth/network'; import { address, artifactIdFromEthersBN, artifactIdToDecStr, decodeArrival, decodeArtifact, decodeArtifactPointValues, decodePlanet, decodePlanetDefaults, decodePlayer, decodeRevealedCoords, decodeUpgradeBranches, locationIdFromEthersBN, locationIdToDecStr, } from '@darkforest_eth/serde'; import { Artifact, ArtifactId, ArtifactType, AutoGasSetting, DiagnosticUpdater, EthAddress, LocationId, Planet, Player, QueuedArrival, RevealedCoords, Setting, Transaction, TransactionId, TxIntent, VoyageId, } from '@darkforest_eth/types'; import { BigNumber as EthersBN, ContractFunction, Event, providers } from 'ethers'; import { EventEmitter } from 'events'; import _ from 'lodash'; import NotificationManager from '../../Frontend/Game/NotificationManager'; import { openConfirmationWindowForTransaction } from '../../Frontend/Game/Popups'; import { getSetting } from '../../Frontend/Utils/SettingsHooks'; import { ContractConstants, ContractEvent, ContractsAPIEvent, PlanetTypeWeightsBySpaceType, } from '../../_types/darkforest/api/ContractsAPITypes'; import { loadDiamondContract } from '../Network/Blockchain'; import { eventLogger, EventType } from '../Network/EventLogger'; interface ContractsApiConfig { connection: EthConnection; contractAddress: EthAddress; } /** * Roughly contains methods that map 1:1 with functions that live in the contract. Responsible for * reading and writing to and from the blockchain. * * @todo don't inherit from {@link EventEmitter}. instead use {@link Monomitter} */ export class ContractsAPI extends EventEmitter { /** * Don't allow users to submit txs if balance falls below this amount/ */ private static readonly MIN_BALANCE = ethToWei(0.002); /** * Instrumented {@link ThrottledConcurrentQueue} for blockchain reads. */ private readonly contractCaller: ContractCaller; /** * Instrumented {@link ThrottledConcurrentQueue} for blockchain writes. */ public readonly txExecutor: TxExecutor; /** * Our connection to the blockchain. In charge of low level networking, and also of the burner * wallet. */ public readonly ethConnection: EthConnection; /** * The contract address is saved on the object upon construction */ private contractAddress: EthAddress; get contract() { return this.ethConnection.getContract(this.contractAddress); } public constructor({ connection, contractAddress }: ContractsApiConfig) { super(); this.contractCaller = new ContractCaller(); this.ethConnection = connection; this.contractAddress = contractAddress; this.txExecutor = new TxExecutor( connection, this.getGasFeeForTransaction.bind(this), this.beforeQueued.bind(this), this.beforeTransaction.bind(this), this.afterTransaction.bind(this) ); this.setupEventListeners(); } /** * We pass this function into {@link TxExecutor} to calculate what gas fee we should use for the * given transaction. The result is either a number, measured in gwei, represented as a string, or * a string representing that we want to use an auto gas setting. */ private getGasFeeForTransaction(tx: Transaction): AutoGasSetting | string { if ( (tx.intent.methodName === 'initializePlayer' || tx.intent.methodName === 'getSpaceShips') && tx.intent.contract.address === this.contract.address ) { return '50'; } const config = { contractAddress: this.contractAddress, account: this.ethConnection.getAddress(), }; return getSetting(config, Setting.GasFeeGwei); } /** * This function is called by {@link TxExecutor} before a transaction is queued. * It gives the client an opportunity to prevent a transaction from being queued based * on business logic or user interaction. * * Reject the promise to prevent the queued transaction from being queued. */ private async beforeQueued( id: TransactionId, intent: TxIntent, overrides?: providers.TransactionRequest ): Promise { const address = this.ethConnection.getAddress(); if (!address) throw new Error("can't send a transaction, no signer"); const balance = await this.ethConnection.loadBalance(address); if (balance.lt(ContractsAPI.MIN_BALANCE)) { const notifsManager = NotificationManager.getInstance(); notifsManager.balanceEmpty(); throw new Error('xDAI balance too low!'); } const gasFeeGwei = EthersBN.from(overrides?.gasPrice || '1000000000'); await openConfirmationWindowForTransaction({ contractAddress: this.contractAddress, connection: this.ethConnection, id, intent, overrides, from: address, gasFeeGwei, }); } /** * This function is called by {@link TxExecutor} before each transaction. It gives the client an * opportunity to prevent a transaction from going through based on business logic or user * interaction. To prevent the queued transaction from being submitted, throw an Error. */ private async beforeTransaction(tx: Transaction): Promise { this.emit(ContractsAPIEvent.TxProcessing, tx); } private async afterTransaction(_txRequest: Transaction, txDiagnosticInfo: unknown) { eventLogger.logEvent(EventType.Transaction, txDiagnosticInfo); } public destroy(): void { this.removeEventListeners(); } private makeCall(contractViewFunction: ContractFunction, args: unknown[] = []): Promise { return this.contractCaller.makeCall(contractViewFunction, args); } public async setupEventListeners(): Promise { const { contract } = this; const filter = { address: contract.address, topics: [ [ contract.filters.ArrivalQueued(null, null, null, null, null).topics, contract.filters.ArtifactActivated(null, null, null).topics, contract.filters.ArtifactDeactivated(null, null, null).topics, contract.filters.ArtifactDeposited(null, null, null).topics, contract.filters.ArtifactFound(null, null, null).topics, contract.filters.ArtifactWithdrawn(null, null, null).topics, contract.filters.LocationRevealed(null, null, null, null).topics, contract.filters.PlanetHatBought(null, null, null).topics, contract.filters.PlanetProspected(null, null).topics, contract.filters.PlanetSilverWithdrawn(null, null, null).topics, contract.filters.PlanetTransferred(null, null, null).topics, contract.filters.PlanetInvaded(null, null).topics, contract.filters.PlanetCaptured(null, null).topics, contract.filters.PlayerInitialized(null, null).topics, contract.filters.AdminOwnershipChanged(null, null).topics, contract.filters.AdminGiveSpaceship(null, null).topics, contract.filters.PauseStateChanged(null).topics, contract.filters.LobbyCreated(null, null).topics, ].map((topicsOrUndefined) => (topicsOrUndefined || [])[0]), ] as Array>, }; const eventHandlers = { [ContractEvent.PauseStateChanged]: (paused: boolean) => { this.emit(ContractsAPIEvent.PauseStateChanged, paused); }, [ContractEvent.AdminOwnershipChanged]: (location: EthersBN, _newOwner: string) => { this.emit(ContractsAPIEvent.PlanetUpdate, locationIdFromEthersBN(location)); }, [ContractEvent.AdminGiveSpaceship]: ( location: EthersBN, _newOwner: string, _type: ArtifactType ) => { this.emit(ContractsAPIEvent.PlanetUpdate, locationIdFromEthersBN(location)); }, [ContractEvent.ArtifactFound]: ( _playerAddr: string, rawArtifactId: EthersBN, loc: EthersBN ) => { const artifactId = artifactIdFromEthersBN(rawArtifactId); this.emit(ContractsAPIEvent.ArtifactUpdate, artifactId); this.emit(ContractsAPIEvent.PlanetUpdate, locationIdFromEthersBN(loc)); }, [ContractEvent.ArtifactDeposited]: ( _playerAddr: string, rawArtifactId: EthersBN, loc: EthersBN ) => { const artifactId = artifactIdFromEthersBN(rawArtifactId); this.emit(ContractsAPIEvent.ArtifactUpdate, artifactId); this.emit(ContractsAPIEvent.PlanetUpdate, locationIdFromEthersBN(loc)); }, [ContractEvent.ArtifactWithdrawn]: ( _playerAddr: string, rawArtifactId: EthersBN, loc: EthersBN ) => { const artifactId = artifactIdFromEthersBN(rawArtifactId); this.emit(ContractsAPIEvent.ArtifactUpdate, artifactId); this.emit(ContractsAPIEvent.PlanetUpdate, locationIdFromEthersBN(loc)); }, [ContractEvent.ArtifactActivated]: ( _playerAddr: string, rawArtifactId: EthersBN, loc: EthersBN ) => { const artifactId = artifactIdFromEthersBN(rawArtifactId); this.emit(ContractsAPIEvent.ArtifactUpdate, artifactId); this.emit(ContractsAPIEvent.PlanetUpdate, locationIdFromEthersBN(loc)); }, [ContractEvent.ArtifactDeactivated]: ( _playerAddr: string, rawArtifactId: EthersBN, loc: EthersBN ) => { const artifactId = artifactIdFromEthersBN(rawArtifactId); this.emit(ContractsAPIEvent.ArtifactUpdate, artifactId); this.emit(ContractsAPIEvent.PlanetUpdate, locationIdFromEthersBN(loc)); }, [ContractEvent.PlayerInitialized]: async (player: string, locRaw: EthersBN, _: Event) => { this.emit(ContractsAPIEvent.PlayerUpdate, address(player)); this.emit(ContractsAPIEvent.PlanetUpdate, locationIdFromEthersBN(locRaw)); this.emit(ContractsAPIEvent.RadiusUpdated); }, [ContractEvent.PlanetTransferred]: async ( _senderAddress: string, planetId: EthersBN, receiverAddress: string, _: Event ) => { this.emit( ContractsAPIEvent.PlanetTransferred, locationIdFromEthersBN(planetId), address(receiverAddress) ); }, [ContractEvent.ArrivalQueued]: async ( playerAddr: string, arrivalId: EthersBN, fromLocRaw: EthersBN, toLocRaw: EthersBN, _artifactIdRaw: EthersBN, _: Event ) => { this.emit( ContractsAPIEvent.ArrivalQueued, arrivalId.toString() as VoyageId, locationIdFromEthersBN(fromLocRaw), locationIdFromEthersBN(toLocRaw) ); this.emit(ContractsAPIEvent.PlayerUpdate, address(playerAddr)); this.emit(ContractsAPIEvent.RadiusUpdated); }, [ContractEvent.PlanetUpgraded]: async ( _playerAddr: string, location: EthersBN, _branch: EthersBN, _toBranchLevel: EthersBN, _: Event ) => { this.emit(ContractsAPIEvent.PlanetUpdate, locationIdFromEthersBN(location)); }, [ContractEvent.PlanetInvaded]: async (_playerAddr: string, location: EthersBN, _: Event) => { this.emit(ContractsAPIEvent.PlanetUpdate, locationIdFromEthersBN(location)); }, [ContractEvent.PlanetCaptured]: async (_playerAddr: string, location: EthersBN, _: Event) => { this.emit(ContractsAPIEvent.PlanetUpdate, locationIdFromEthersBN(location)); }, [ContractEvent.PlanetHatBought]: async ( _playerAddress: string, location: EthersBN, _: Event ) => this.emit(ContractsAPIEvent.PlanetUpdate, locationIdFromEthersBN(location)), [ContractEvent.LocationRevealed]: async ( revealerAddr: string, location: EthersBN, _x: EthersBN, _y: EthersBN, _: Event ) => { this.emit(ContractsAPIEvent.PlanetUpdate, locationIdFromEthersBN(location)); this.emit( ContractsAPIEvent.LocationRevealed, locationIdFromEthersBN(location), address(revealerAddr.toLowerCase()) ); this.emit(ContractsAPIEvent.PlayerUpdate, address(revealerAddr)); }, [ContractEvent.PlanetSilverWithdrawn]: async ( player: string, location: EthersBN, _amount: EthersBN, _: Event ) => { this.emit(ContractsAPIEvent.PlanetUpdate, locationIdFromEthersBN(location)); this.emit(ContractsAPIEvent.PlayerUpdate, address(player)); }, [ContractEvent.LobbyCreated]: (ownerAddr: string, lobbyAddr: string) => { this.emit(ContractsAPIEvent.LobbyCreated, address(ownerAddr), address(lobbyAddr)); }, }; this.ethConnection.subscribeToContractEvents(contract, eventHandlers, filter); } public removeEventListeners(): void { const { contract } = this; contract.removeAllListeners(ContractEvent.PlayerInitialized); contract.removeAllListeners(ContractEvent.ArrivalQueued); contract.removeAllListeners(ContractEvent.PlanetUpgraded); contract.removeAllListeners(ContractEvent.PlanetHatBought); contract.removeAllListeners(ContractEvent.PlanetTransferred); contract.removeAllListeners(ContractEvent.ArtifactFound); contract.removeAllListeners(ContractEvent.ArtifactDeposited); contract.removeAllListeners(ContractEvent.ArtifactWithdrawn); contract.removeAllListeners(ContractEvent.ArtifactActivated); contract.removeAllListeners(ContractEvent.ArtifactDeactivated); contract.removeAllListeners(ContractEvent.LocationRevealed); contract.removeAllListeners(ContractEvent.PlanetSilverWithdrawn); contract.removeAllListeners(ContractEvent.PlanetInvaded); contract.removeAllListeners(ContractEvent.PlanetCaptured); } public getContractAddress(): EthAddress { return this.contractAddress; } async getConstants(): Promise { const { DISABLE_ZK_CHECKS, PLANETHASH_KEY, SPACETYPE_KEY, BIOMEBASE_KEY, PERLIN_LENGTH_SCALE, PERLIN_MIRROR_X, PERLIN_MIRROR_Y, } = await this.makeCall(this.contract.getSnarkConstants); const { ADMIN_CAN_ADD_PLANETS, WORLD_RADIUS_LOCKED, WORLD_RADIUS_MIN, MAX_NATURAL_PLANET_LEVEL, TIME_FACTOR_HUNDREDTHS, PERLIN_THRESHOLD_1, PERLIN_THRESHOLD_2, PERLIN_THRESHOLD_3, INIT_PERLIN_MIN, INIT_PERLIN_MAX, SPAWN_RIM_AREA, BIOME_THRESHOLD_1, BIOME_THRESHOLD_2, SILVER_SCORE_VALUE, // TODO: Actually put this in game constants // PLANET_LEVEL_THRESHOLDS, PLANET_RARITY, PLANET_TRANSFER_ENABLED, PHOTOID_ACTIVATION_DELAY, LOCATION_REVEAL_COOLDOWN, SPACE_JUNK_ENABLED, SPACE_JUNK_LIMIT, PLANET_LEVEL_JUNK, ABANDON_SPEED_CHANGE_PERCENT, ABANDON_RANGE_CHANGE_PERCENT, // Capture Zones GAME_START_BLOCK, CAPTURE_ZONES_ENABLED, CAPTURE_ZONE_COUNT, CAPTURE_ZONE_CHANGE_BLOCK_INTERVAL, CAPTURE_ZONE_RADIUS, CAPTURE_ZONE_PLANET_LEVEL_SCORE, CAPTURE_ZONE_HOLD_BLOCKS_REQUIRED, CAPTURE_ZONES_PER_5000_WORLD_RADIUS, } = await this.makeCall(this.contract.getGameConstants); const TOKEN_MINT_END_SECONDS = ( await this.makeCall(this.contract.TOKEN_MINT_END_TIMESTAMP) ).toNumber(); const adminAddress = address(await this.makeCall(this.contract.adminAddress)); const upgrades = decodeUpgradeBranches(await this.makeCall(this.contract.getUpgrades)); const PLANET_TYPE_WEIGHTS: PlanetTypeWeightsBySpaceType = await this.makeCall(this.contract.getTypeWeights); const rawPointValues = await this.makeCall(this.contract.getArtifactPointValues); const ARTIFACT_POINT_VALUES = decodeArtifactPointValues(rawPointValues); const planetDefaults = decodePlanetDefaults(await this.makeCall(this.contract.getDefaultStats)); const planetLevelThresholds = ( await this.makeCall(this.contract.getPlanetLevelThresholds) ).map((x: EthersBN) => x.toNumber()); const planetCumulativeRarities = ( await this.makeCall(this.contract.getCumulativeRarities) ).map((x: EthersBN) => x.toNumber()); const constants: ContractConstants = { ADMIN_CAN_ADD_PLANETS, WORLD_RADIUS_LOCKED, WORLD_RADIUS_MIN: WORLD_RADIUS_MIN.toNumber(), DISABLE_ZK_CHECKS, PLANETHASH_KEY: PLANETHASH_KEY.toNumber(), SPACETYPE_KEY: SPACETYPE_KEY.toNumber(), BIOMEBASE_KEY: BIOMEBASE_KEY.toNumber(), PERLIN_LENGTH_SCALE: PERLIN_LENGTH_SCALE.toNumber(), PERLIN_MIRROR_X, PERLIN_MIRROR_Y, CLAIM_PLANET_COOLDOWN: 0, TOKEN_MINT_END_SECONDS, MAX_NATURAL_PLANET_LEVEL: MAX_NATURAL_PLANET_LEVEL.toNumber(), TIME_FACTOR_HUNDREDTHS: TIME_FACTOR_HUNDREDTHS.toNumber(), PERLIN_THRESHOLD_1: PERLIN_THRESHOLD_1.toNumber(), PERLIN_THRESHOLD_2: PERLIN_THRESHOLD_2.toNumber(), PERLIN_THRESHOLD_3: PERLIN_THRESHOLD_3.toNumber(), INIT_PERLIN_MIN: INIT_PERLIN_MIN.toNumber(), INIT_PERLIN_MAX: INIT_PERLIN_MAX.toNumber(), BIOME_THRESHOLD_1: BIOME_THRESHOLD_1.toNumber(), BIOME_THRESHOLD_2: BIOME_THRESHOLD_2.toNumber(), SILVER_SCORE_VALUE: SILVER_SCORE_VALUE.toNumber(), PLANET_LEVEL_THRESHOLDS: [ planetLevelThresholds[0], planetLevelThresholds[1], planetLevelThresholds[2], planetLevelThresholds[3], planetLevelThresholds[4], planetLevelThresholds[5], planetLevelThresholds[6], planetLevelThresholds[7], planetLevelThresholds[8], planetLevelThresholds[9], ], PLANET_RARITY: PLANET_RARITY.toNumber(), PLANET_TRANSFER_ENABLED, PLANET_TYPE_WEIGHTS, ARTIFACT_POINT_VALUES, SPACE_JUNK_ENABLED, SPACE_JUNK_LIMIT: SPACE_JUNK_LIMIT.toNumber(), PLANET_LEVEL_JUNK: [ PLANET_LEVEL_JUNK[0].toNumber(), PLANET_LEVEL_JUNK[1].toNumber(), PLANET_LEVEL_JUNK[2].toNumber(), PLANET_LEVEL_JUNK[3].toNumber(), PLANET_LEVEL_JUNK[4].toNumber(), PLANET_LEVEL_JUNK[5].toNumber(), PLANET_LEVEL_JUNK[6].toNumber(), PLANET_LEVEL_JUNK[7].toNumber(), PLANET_LEVEL_JUNK[8].toNumber(), PLANET_LEVEL_JUNK[9].toNumber(), ], ABANDON_SPEED_CHANGE_PERCENT: ABANDON_RANGE_CHANGE_PERCENT.toNumber(), ABANDON_RANGE_CHANGE_PERCENT: ABANDON_SPEED_CHANGE_PERCENT.toNumber(), PHOTOID_ACTIVATION_DELAY: PHOTOID_ACTIVATION_DELAY.toNumber(), SPAWN_RIM_AREA: SPAWN_RIM_AREA.toNumber(), LOCATION_REVEAL_COOLDOWN: LOCATION_REVEAL_COOLDOWN.toNumber(), defaultPopulationCap: planetDefaults.populationCap, defaultPopulationGrowth: planetDefaults.populationGrowth, defaultRange: planetDefaults.range, defaultSpeed: planetDefaults.speed, defaultDefense: planetDefaults.defense, defaultSilverGrowth: planetDefaults.silverGrowth, defaultSilverCap: planetDefaults.silverCap, defaultBarbarianPercentage: planetDefaults.barbarianPercentage, planetLevelThresholds, planetCumulativeRarities, upgrades, adminAddress, // Capture Zones GAME_START_BLOCK: GAME_START_BLOCK.toNumber(), CAPTURE_ZONES_ENABLED, CAPTURE_ZONE_COUNT: CAPTURE_ZONE_COUNT.toNumber(), CAPTURE_ZONE_CHANGE_BLOCK_INTERVAL: CAPTURE_ZONE_CHANGE_BLOCK_INTERVAL.toNumber(), CAPTURE_ZONE_RADIUS: CAPTURE_ZONE_RADIUS.toNumber(), CAPTURE_ZONE_PLANET_LEVEL_SCORE: [ CAPTURE_ZONE_PLANET_LEVEL_SCORE[0].toNumber(), CAPTURE_ZONE_PLANET_LEVEL_SCORE[1].toNumber(), CAPTURE_ZONE_PLANET_LEVEL_SCORE[2].toNumber(), CAPTURE_ZONE_PLANET_LEVEL_SCORE[3].toNumber(), CAPTURE_ZONE_PLANET_LEVEL_SCORE[4].toNumber(), CAPTURE_ZONE_PLANET_LEVEL_SCORE[5].toNumber(), CAPTURE_ZONE_PLANET_LEVEL_SCORE[6].toNumber(), CAPTURE_ZONE_PLANET_LEVEL_SCORE[7].toNumber(), CAPTURE_ZONE_PLANET_LEVEL_SCORE[8].toNumber(), CAPTURE_ZONE_PLANET_LEVEL_SCORE[9].toNumber(), ], CAPTURE_ZONE_HOLD_BLOCKS_REQUIRED: CAPTURE_ZONE_HOLD_BLOCKS_REQUIRED.toNumber(), CAPTURE_ZONES_PER_5000_WORLD_RADIUS: CAPTURE_ZONES_PER_5000_WORLD_RADIUS.toNumber(), }; return constants; } public async getPlayers( onProgress?: (fractionCompleted: number) => void ): Promise> { const nPlayers: number = (await this.makeCall(this.contract.getNPlayers)).toNumber(); const players = await aggregateBulkGetter( nPlayers, 200, async (start, end) => (await this.makeCall(this.contract.bulkGetPlayers, [start, end])).map(decodePlayer), onProgress ); const playerMap: Map = new Map(); for (const player of players) { playerMap.set(player.address, player); } return playerMap; } public async getPlayerById(playerId: EthAddress): Promise { const rawPlayer = await this.makeCall(this.contract.players, [playerId]); if (!rawPlayer.isInitialized) return undefined; const player = decodePlayer(rawPlayer); return player; } public async getWorldRadius(): Promise { const radius = (await this.makeCall(this.contract.worldRadius)).toNumber(); return radius; } // timestamp since epoch (in seconds) public async getTokenMintEndTimestamp(): Promise { const timestamp = ( await this.makeCall(this.contract.TOKEN_MINT_END_TIMESTAMP) ).toNumber(); return timestamp; } public async getArrival(arrivalId: number): Promise { const rawArrival = await this.makeCall(this.contract.planetArrivals, [arrivalId]); return decodeArrival(rawArrival); } public async getArrivalsForPlanet(planetId: LocationId): Promise { const events = ( await this.makeCall(this.contract.getPlanetArrivals, [locationIdToDecStr(planetId)]) ).map(decodeArrival); return events; } public async getAllArrivals( planetsToLoad: LocationId[], onProgress?: (fractionCompleted: number) => void ): Promise { const arrivalsUnflattened = await aggregateBulkGetter( planetsToLoad.length, 200, async (start, end) => { return ( await this.makeCall(this.contract.bulkGetPlanetArrivalsByIds, [ planetsToLoad.slice(start, end).map(locationIdToDecStr), ]) ).map((arrivals) => arrivals.map(decodeArrival)); }, onProgress ); return _.flatten(arrivalsUnflattened); } public async getTouchedPlanetIds( startingAt: number, onProgress?: (fractionCompleted: number) => void ): Promise { const nPlanets: number = (await this.makeCall(this.contract.getNPlanets)).toNumber(); const planetIds = await aggregateBulkGetter( nPlanets - startingAt, 1000, async (start, end) => await this.makeCall(this.contract.bulkGetPlanetIds, [start + startingAt, end + startingAt]), onProgress ); return planetIds.map(locationIdFromEthersBN); } public async getRevealedCoordsByIdIfExists( planetId: LocationId ): Promise { const decStrId = locationIdToDecStr(planetId); const rawRevealedCoords = await this.makeCall(this.contract.revealedCoords, [decStrId]); const ret = decodeRevealedCoords(rawRevealedCoords); if (ret.hash === EMPTY_LOCATION_ID) { return undefined; } return ret; } public async getIsPaused(): Promise { return this.makeCall(this.contract.paused); } public async getRevealedPlanetsCoords( startingAt: number, onProgressIds?: (fractionCompleted: number) => void, onProgressCoords?: (fractionCompleted: number) => void ): Promise { const nRevealedPlanets: number = ( await this.makeCall(this.contract.getNRevealedPlanets) ).toNumber(); const rawRevealedPlanetIds = await aggregateBulkGetter( nRevealedPlanets - startingAt, 500, async (start, end) => await this.makeCall(this.contract.bulkGetRevealedPlanetIds, [ start + startingAt, end + startingAt, ]), onProgressIds ); const rawRevealedCoords = await aggregateBulkGetter( rawRevealedPlanetIds.length, 500, async (start, end) => await this.makeCall(this.contract.bulkGetRevealedCoordsByIds, [ rawRevealedPlanetIds.slice(start, end), ]), onProgressCoords ); return rawRevealedCoords.map(decodeRevealedCoords); } public async bulkGetPlanets( toLoadPlanets: LocationId[], onProgressPlanet?: (fractionCompleted: number) => void, onProgressMetadata?: (fractionCompleted: number) => void ): Promise> { const rawPlanets = await aggregateBulkGetter( toLoadPlanets.length, 200, async (start, end) => await this.makeCall(this.contract.bulkGetPlanetsByIds, [ toLoadPlanets.slice(start, end).map(locationIdToDecStr), ]), onProgressPlanet ); const rawPlanetsExtendedInfo = await aggregateBulkGetter( toLoadPlanets.length, 200, async (start, end) => await this.makeCall(this.contract.bulkGetPlanetsExtendedInfoByIds, [ toLoadPlanets.slice(start, end).map(locationIdToDecStr), ]), (fractionCompleted) => { if (!onProgressMetadata) return; onProgressMetadata(fractionCompleted / 2); } ); const rawPlanetsExtendedInfo2 = await aggregateBulkGetter( toLoadPlanets.length, 200, async (start, end) => await this.makeCall(this.contract.bulkGetPlanetsExtendedInfo2ByIds, [ toLoadPlanets.slice(start, end).map(locationIdToDecStr), ]), (fractionCompleted) => { if (!onProgressMetadata) return; onProgressMetadata(0.5 + fractionCompleted / 2); } ); const planets: Map = new Map(); for (let i = 0; i < toLoadPlanets.length; i += 1) { if (!!rawPlanets[i] && !!rawPlanetsExtendedInfo[i]) { const planet = decodePlanet( locationIdToDecStr(toLoadPlanets[i]), rawPlanets[i], rawPlanetsExtendedInfo[i], rawPlanetsExtendedInfo2[i] ); planet.transactions = new TxCollection(); planets.set(planet.locationId, planet); } } return planets; } public async getPlanetById(planetId: LocationId): Promise { const decStrId = locationIdToDecStr(planetId); const rawExtendedInfo = await this.makeCall(this.contract.planetsExtendedInfo, [decStrId]); const rawExtendedInfo2 = await this.makeCall(this.contract.planetsExtendedInfo2, [decStrId]); if (!rawExtendedInfo[0]) return undefined; // planetExtendedInfo.isInitialized is false if (!rawExtendedInfo2[0]) return undefined; // planetExtendedInfo.isInitialized is false const rawPlanet = await this.makeCall(this.contract.planets, [decStrId]); return decodePlanet(decStrId, rawPlanet, rawExtendedInfo, rawExtendedInfo2); } public async getArtifactById(artifactId: ArtifactId): Promise { const exists = await this.makeCall(this.contract.doesArtifactExist, [ artifactIdToDecStr(artifactId), ]); if (!exists) return undefined; const rawArtifact = await this.makeCall(this.contract.getArtifactById, [ artifactIdToDecStr(artifactId), ]); const artifact = decodeArtifact(rawArtifact); artifact.transactions = new TxCollection(); return artifact; } public async bulkGetArtifactsOnPlanets( locationIds: LocationId[], onProgress?: (fractionCompleted: number) => void ): Promise { const rawArtifacts = await aggregateBulkGetter( locationIds.length, 200, async (start, end) => await this.makeCall(this.contract.bulkGetPlanetArtifacts, [ locationIds.slice(start, end).map(locationIdToDecStr), ]), onProgress ); return rawArtifacts.map((rawArtifactArray) => { return rawArtifactArray.map(decodeArtifact); }); } public async bulkGetArtifacts( artifactIds: ArtifactId[], onProgress?: (fractionCompleted: number) => void ): Promise { const rawArtifacts = await aggregateBulkGetter( artifactIds.length, 200, async (start, end) => await this.makeCall(this.contract.bulkGetArtifactsByIds, [ artifactIds.slice(start, end).map(artifactIdToDecStr), ]), onProgress ); const ret: Artifact[] = rawArtifacts.map(decodeArtifact); ret.forEach((a) => (a.transactions = new TxCollection())); return ret; } public async getPlayerArtifacts( playerId?: EthAddress, onProgress?: (percent: number) => void ): Promise { if (playerId === undefined) return []; const myArtifactIds = (await this.makeCall(this.contract.getPlayerArtifactIds, [playerId])).map( artifactIdFromEthersBN ); return this.bulkGetArtifacts(myArtifactIds, onProgress); } public setDiagnosticUpdater(diagnosticUpdater?: DiagnosticUpdater) { this.contractCaller.setDiagnosticUpdater(diagnosticUpdater); this.txExecutor?.setDiagnosticUpdater(diagnosticUpdater); this.ethConnection.setDiagnosticUpdater(diagnosticUpdater); } public async submitTransaction( txIntent: T, overrides?: providers.TransactionRequest ): Promise> { const queuedTx = await this.txExecutor.queueTransaction(txIntent, overrides); this.emit(ContractsAPIEvent.TxQueued, queuedTx); // TODO: Why is this setTimeout here? Can it be removed? setTimeout(() => this.emitTransactionEvents(queuedTx), 0); return queuedTx; } /** * Remove a transaction from the queue. */ public cancelTransaction(tx: Transaction): void { this.txExecutor.dequeueTransction(tx); this.emit(ContractsAPIEvent.TxCancelled, tx); } /** * Make sure this transaction is the next to be executed. */ public prioritizeTransaction(tx: Transaction): void { this.txExecutor.prioritizeTransaction(tx); this.emit(ContractsAPIEvent.TxPrioritized, tx); } /** * This is a strange interface between the transaction queue system and the rest of the game. The * strange thing about it is that introduces another way by which transactions are pushed into the * game - these {@code ContractsAPIEvent} events. */ public emitTransactionEvents(tx: Transaction): void { tx.submittedPromise .then(() => { this.emit(ContractsAPIEvent.TxSubmitted, tx); }) .catch(() => { this.emit(ContractsAPIEvent.TxErrored, tx); }); tx.confirmedPromise .then(() => { this.emit(ContractsAPIEvent.TxConfirmed, tx); }) .catch(() => { this.emit(ContractsAPIEvent.TxErrored, tx); }); } public getAddress() { return this.ethConnection.getAddress(); } } export async function makeContractsAPI({ connection, contractAddress, }: ContractsApiConfig): Promise { await connection.loadContract(contractAddress, loadDiamondContract); return new ContractsAPI({ connection, contractAddress }); } ================================================ FILE: src/Backend/GameLogic/GameManager.ts ================================================ import { BLOCK_EXPLORER_URL, CONTRACT_PRECISION, EMPTY_ADDRESS, MIN_PLANET_LEVEL, } from '@darkforest_eth/constants'; import type { DarkForest } from '@darkforest_eth/contracts/typechain'; import { monomitter, Monomitter, Subscription } from '@darkforest_eth/events'; import { getRange, isActivated, isLocatable, isSpaceShip, timeUntilNextBroadcastAvailable, } from '@darkforest_eth/gamelogic'; import { fakeHash, mimcHash, perlin } from '@darkforest_eth/hashing'; import { createContract, EthConnection, ThrottledConcurrentQueue, verifySignature, weiToEth, } from '@darkforest_eth/network'; import { getPlanetName } from '@darkforest_eth/procedural'; import { artifactIdToDecStr, isUnconfirmedActivateArtifactTx, isUnconfirmedBuyHatTx, isUnconfirmedCapturePlanetTx, isUnconfirmedDeactivateArtifactTx, isUnconfirmedDepositArtifactTx, isUnconfirmedFindArtifactTx, isUnconfirmedInitTx, isUnconfirmedInvadePlanetTx, isUnconfirmedMoveTx, isUnconfirmedProspectPlanetTx, isUnconfirmedRevealTx, isUnconfirmedUpgradeTx, isUnconfirmedWithdrawArtifactTx, isUnconfirmedWithdrawSilverTx, locationIdFromBigInt, locationIdToDecStr, } from '@darkforest_eth/serde'; import { Artifact, ArtifactId, ArtifactRarity, ArtifactType, CaptureZone, Chunk, ClaimedCoords, ClaimedLocation, Diagnostics, EthAddress, LocatablePlanet, LocationId, NetworkHealthSummary, Planet, PlanetLevel, PlanetMessageType, PlanetType, Player, QueuedArrival, Radii, Rectangle, RevealedCoords, RevealedLocation, Setting, SignedMessage, SpaceType, Transaction, TxIntent, UnconfirmedActivateArtifact, UnconfirmedBuyHat, UnconfirmedCapturePlanet, UnconfirmedDeactivateArtifact, UnconfirmedDepositArtifact, UnconfirmedFindArtifact, UnconfirmedInit, UnconfirmedInvadePlanet, UnconfirmedMove, UnconfirmedPlanetTransfer, UnconfirmedProspectPlanet, UnconfirmedReveal, UnconfirmedUpgrade, UnconfirmedWithdrawArtifact, UnconfirmedWithdrawSilver, Upgrade, VoyageId, WorldCoords, WorldLocation, Wormhole, } from '@darkforest_eth/types'; import bigInt, { BigInteger } from 'big-integer'; import delay from 'delay'; import { BigNumber, Contract, ContractInterface, providers } from 'ethers'; import { EventEmitter } from 'events'; import NotificationManager from '../../Frontend/Game/NotificationManager'; import { MIN_CHUNK_SIZE } from '../../Frontend/Utils/constants'; import { Diff, generateDiffEmitter, getDisposableEmitter } from '../../Frontend/Utils/EmitterUtils'; import { getBooleanSetting, getNumberSetting, pollSetting, setBooleanSetting, setSetting, settingChanged$, } from '../../Frontend/Utils/SettingsHooks'; import { TerminalTextStyle } from '../../Frontend/Utils/TerminalTypes'; import UIEmitter from '../../Frontend/Utils/UIEmitter'; import { TerminalHandle } from '../../Frontend/Views/Terminal'; import { ContractConstants, ContractsAPIEvent, MoveArgIdxs, MoveArgs, ZKArgIdx, } from '../../_types/darkforest/api/ContractsAPITypes'; import { AddressTwitterMap } from '../../_types/darkforest/api/UtilityServerAPITypes'; import { HashConfig, RevealCountdownInfo } from '../../_types/global/GlobalTypes'; import MinerManager, { HomePlanetMinerChunkStore, MinerManagerEvent } from '../Miner/MinerManager'; import { MiningPattern, SpiralPattern, SwissCheesePattern, TowardsCenterPattern, TowardsCenterPatternV2, } from '../Miner/MiningPatterns'; import { eventLogger, EventType } from '../Network/EventLogger'; import { loadLeaderboard } from '../Network/LeaderboardApi'; import { addMessage, deleteMessages, getMessagesOnPlanets } from '../Network/MessageAPI'; import { loadNetworkHealth } from '../Network/NetworkHealthApi'; import { disconnectTwitter, getAllTwitters, verifyTwitterHandle, } from '../Network/UtilityServerAPI'; import { SerializedPlugin } from '../Plugins/SerializedPlugin'; import PersistentChunkStore from '../Storage/PersistentChunkStore'; import { easeInAnimation, emojiEaseOutAnimation } from '../Utils/Animation'; import SnarkArgsHelper from '../Utils/SnarkArgsHelper'; import { hexifyBigIntNestedArray } from '../Utils/Utils'; import { getEmojiMessage } from './ArrivalUtils'; import { CaptureZoneGenerator, CaptureZonesGeneratedEvent } from './CaptureZoneGenerator'; import { ContractsAPI, makeContractsAPI } from './ContractsAPI'; import { GameObjects } from './GameObjects'; import { InitialGameStateDownloader } from './InitialGameStateDownloader'; export enum GameManagerEvent { PlanetUpdate = 'PlanetUpdate', DiscoveredNewChunk = 'DiscoveredNewChunk', InitializedPlayer = 'InitializedPlayer', InitializedPlayerError = 'InitializedPlayerError', ArtifactUpdate = 'ArtifactUpdate', Moved = 'Moved', } class GameManager extends EventEmitter { /** * This variable contains the internal state of objects that live in the game world. */ private readonly entityStore: GameObjects; /** * Kind of hacky, but we store a reference to the terminal that the player sees when the initially * load into the game. This is the same exact terminal that appears inside the collapsable right * bar of the game. */ private readonly terminal: React.MutableRefObject; /** * The ethereum address of the player who is currently logged in. We support 'no account', * represented by `undefined` in the case when you want to simply load the game state from the * contract and view it without be able to make any moves. */ private readonly account: EthAddress | undefined; /** * Map from ethereum addresses to player objects. This isn't stored in {@link GameObjects}, * because it's not techincally an entity that exists in the world. A player just controls planets * and artifacts that do exist in the world. * * @todo move this into a new `Players` class. */ private readonly players: Map; /** * Allows us to make contract calls, and execute transactions. Be careful about how you use this * guy. You don't want to cause your client to send an excessive amount of traffic to whatever * node you're connected to. * * Interacting with the blockchain isn't free, and we need to be mindful about about the way our * application interacts with the blockchain. The current rate limiting strategy consists of three * points: * * - data that needs to be fetched often should be fetched in bulk. * - rate limit smart contract calls (reads from the blockchain), implemented by * {@link ContractCaller} and transactions (writes to the blockchain on behalf of the player), * implemented by {@link TxExecutor} via two separately tuned {@link ThrottledConcurrentQueue}s. */ private readonly contractsAPI: ContractsAPI; /** * An object that syncs any newly added or deleted chunks to the player's IndexedDB. * * @todo it also persists other game data to IndexedDB. This class needs to be renamed `GameSaver` * or something like that. */ private readonly persistentChunkStore: PersistentChunkStore; /** * Responsible for generating snark proofs. */ private readonly snarkHelper: SnarkArgsHelper; /** * In debug builds of the game, we can connect to a set of contracts deployed to a local * blockchain, which are tweaked to not verify planet hashes, meaning we can use a faster hash * function with similar properties to mimc. This allows us to mine the map faster in debug mode. * * @todo move this into a separate `GameConfiguration` class. */ private readonly useMockHash: boolean; /** * Game parameters set by the contract. Stuff like perlin keys, which are important for mining the * correct universe, or the time multiplier, which allows us to tune how quickly voyages go. * * @todo move this into a separate `GameConfiguration` class. */ private readonly contractConstants: ContractConstants; private paused: boolean; /** * @todo change this to the correct timestamp each round. */ private readonly endTimeSeconds: number = 1948939200; // new Date("2031-10-05T04:00:00.000Z").getTime() / 1000 /** * An interface to the blockchain that is a little bit lower-level than {@link ContractsAPI}. It * allows us to do basic operations such as wait for a transaction to complete, check the player's * address and balance, etc. */ private readonly ethConnection: EthConnection; /** * Each round we change the hash configuration of the game. The hash configuration is download * from the blockchain, and essentially acts as a salt, permuting the universe into a unique * configuration for each new round. * * @todo deduplicate this and `useMockHash` somehow. */ private readonly hashConfig: HashConfig; /** * The aforementioned hash function. In debug mode where `DISABLE_ZK_CHECKS` is on, we use a * faster hash function. Othewise, in production mode, use MiMC hash (https://byt3bit.github.io/primesym/). */ private readonly planetHashMimc: (...inputs: number[]) => BigInteger; /** * Whenever we refresh the players twitter accounts or scores, we publish an event here. */ public readonly playersUpdated$: Monomitter; /** * Handle to an interval that periodically uploads diagnostic information from this client. */ private diagnosticsInterval: ReturnType; /** * Handle to an interval that periodically refreshes some information about the player from the * blockchain. * * @todo move this into a new `PlayerState` class. */ private playerInterval: ReturnType; /** * Handle to an interval that periodically refreshes the scoreboard from our webserver. */ private scoreboardInterval: ReturnType; /** * Handle to an interval that periodically refreshes the network's health from our webserver. */ private networkHealthInterval: ReturnType; /** * Manages the process of mining new space territory. */ private minerManager?: MinerManager; /** * Continuously updated value representing the total hashes per second that the game is currently * mining the universe at. * * @todo keep this in {@link MinerManager} */ private hashRate: number; /** * The spawn location of the current player. * * @todo, make this smarter somehow. It's really annoying to have to import world coordinates, and * get them wrong or something. Maybe we need to mark a planet, once it's been initialized * contract-side, as the homeworld of the user who initialized on it. That way, when you import a * new account into the game, and you import map data that contains your home planet, the client * would be able to automatically detect which planet is the player's home planet. * * @todo move this into a new `PlayerState` class. */ private homeLocation: WorldLocation | undefined; /** * Sometimes the universe gets bigger... Sometimes it doesn't. * * @todo move this into a new `GameConfiguration` class. */ private worldRadius: number; /** * Emits whenever we load the network health summary from the webserver, which is derived from * diagnostics that the client sends up to the webserver as well. */ public networkHealth$: Monomitter; public paused$: Monomitter; /** * Diagnostic information about the game. */ private diagnostics: Diagnostics; /** * Subscription to act on setting changes */ private settingsSubscription: Subscription | undefined; /** * Setting to allow players to start game without plugins that were running during the previous * run of the game client. By default, the game launches plugins that were running that were * running when the game was last closed. */ private safeMode: boolean; public get planetRarity(): number { return this.contractConstants.PLANET_RARITY; } /** * Generates capture zones. */ private captureZoneGenerator: CaptureZoneGenerator | undefined; private constructor( terminal: React.MutableRefObject, account: EthAddress | undefined, players: Map, touchedPlanets: Map, allTouchedPlanetIds: Set, revealedCoords: Map, claimedCoords: Map, worldRadius: number, unprocessedArrivals: Map, unprocessedPlanetArrivalIds: Map, contractsAPI: ContractsAPI, contractConstants: ContractConstants, persistentChunkStore: PersistentChunkStore, snarkHelper: SnarkArgsHelper, homeLocation: WorldLocation | undefined, useMockHash: boolean, artifacts: Map, ethConnection: EthConnection, paused: boolean ) { super(); this.diagnostics = { rpcUrl: 'unknown', totalPlanets: 0, visiblePlanets: 0, visibleChunks: 0, fps: 0, chunkUpdates: 0, callsInQueue: 0, totalCalls: 0, totalTransactions: 0, transactionsInQueue: 0, totalChunks: 0, width: 0, height: 0, }; this.terminal = terminal; this.account = account; this.players = players; this.worldRadius = worldRadius; this.networkHealth$ = monomitter(true); this.paused$ = monomitter(true); this.playersUpdated$ = monomitter(); if (contractConstants.CAPTURE_ZONES_ENABLED) { this.captureZoneGenerator = new CaptureZoneGenerator( this, contractConstants.GAME_START_BLOCK, contractConstants.CAPTURE_ZONE_CHANGE_BLOCK_INTERVAL ); } this.hashConfig = { planetHashKey: contractConstants.PLANETHASH_KEY, spaceTypeKey: contractConstants.SPACETYPE_KEY, biomebaseKey: contractConstants.BIOMEBASE_KEY, perlinLengthScale: contractConstants.PERLIN_LENGTH_SCALE, perlinMirrorX: contractConstants.PERLIN_MIRROR_X, perlinMirrorY: contractConstants.PERLIN_MIRROR_Y, planetRarity: contractConstants.PLANET_RARITY, }; this.planetHashMimc = useMockHash ? fakeHash(this.hashConfig.planetRarity) : mimcHash(this.hashConfig.planetHashKey); this.contractConstants = contractConstants; this.homeLocation = homeLocation; const revealedLocations = new Map(); for (const [locationId, coords] of revealedCoords) { const planet = touchedPlanets.get(locationId); if (planet) { const location: WorldLocation = { hash: locationId, coords, perlin: planet.perlin, biomebase: this.biomebasePerlin(coords, true), }; revealedLocations.set(locationId, { ...location, revealer: coords.revealer }); } } const claimedLocations = new Map(); for (const [locationId, coords] of claimedCoords) { const planet = touchedPlanets.get(locationId); if (planet) { const location: WorldLocation = { hash: locationId, coords, perlin: planet.perlin, biomebase: this.biomebasePerlin(coords, true), }; const revealedLocation = { ...location, revealer: coords.revealer }; revealedLocations.set(locationId, revealedLocation); claimedLocations.set(locationId, revealedLocation); } } this.entityStore = new GameObjects( account, touchedPlanets, allTouchedPlanetIds, revealedLocations, claimedLocations, artifacts, persistentChunkStore.allChunks(), unprocessedArrivals, unprocessedPlanetArrivalIds, contractConstants, worldRadius ); this.contractsAPI = contractsAPI; this.persistentChunkStore = persistentChunkStore; this.snarkHelper = snarkHelper; this.useMockHash = useMockHash; this.paused = paused; this.ethConnection = ethConnection; this.diagnosticsInterval = setInterval(this.uploadDiagnostics.bind(this), 10_000); this.scoreboardInterval = setInterval(this.refreshScoreboard.bind(this), 10_000); this.networkHealthInterval = setInterval(this.refreshNetworkHealth.bind(this), 10_000); this.playerInterval = setInterval(() => { if (this.account) { this.hardRefreshPlayer(this.account); } }, 5000); this.hashRate = 0; this.settingsSubscription = settingChanged$.subscribe((setting: Setting) => { if (setting === Setting.MiningCores) { if (this.minerManager) { const config = { contractAddress: this.getContractAddress(), account: this.account, }; const cores = getNumberSetting(config, Setting.MiningCores); this.minerManager.setCores(cores); } } }); this.refreshScoreboard(); this.refreshNetworkHealth(); this.getSpaceships(); this.safeMode = false; } private async uploadDiagnostics() { eventLogger.logEvent(EventType.Diagnostics, this.diagnostics); } private async refreshNetworkHealth() { try { this.networkHealth$.publish(await loadNetworkHealth()); } catch (e) { // @todo - what do we do if we can't connect to the webserver } } private async refreshScoreboard() { try { const leaderboard = await loadLeaderboard(); for (const entry of leaderboard.entries) { const player = this.players.get(entry.ethAddress); if (player) { // current player's score is updated via `this.playerInterval` if (player.address !== this.account && entry.score !== undefined) { player.score = entry.score; } } } this.playersUpdated$.publish(); } catch (e) { // @todo - what do we do if we can't connect to the webserver? in general this should be a // valid state of affairs because arenas is a thing. } } public getEthConnection() { return this.ethConnection; } public destroy(): void { // removes singletons of ContractsAPI, LocalStorageManager, MinerManager if (this.minerManager) { this.minerManager.removeAllListeners(MinerManagerEvent.DiscoveredNewChunk); this.minerManager.destroy(); } this.contractsAPI.destroy(); this.persistentChunkStore.destroy(); clearInterval(this.playerInterval); clearInterval(this.diagnosticsInterval); clearInterval(this.scoreboardInterval); clearInterval(this.networkHealthInterval); this.settingsSubscription?.unsubscribe(); } static async create({ connection, terminal, contractAddress, }: { connection: EthConnection; terminal: React.MutableRefObject; contractAddress: EthAddress; }): Promise { if (!terminal.current) { throw new Error('you must pass in a handle to a terminal'); } const account = connection.getAddress(); if (!account) { throw new Error('no account on eth connection'); } const gameStateDownloader = new InitialGameStateDownloader(terminal.current); const contractsAPI = await makeContractsAPI({ connection, contractAddress }); terminal.current?.println('Loading game data from disk...'); const persistentChunkStore = await PersistentChunkStore.create({ account, contractAddress }); terminal.current?.println('Downloading data from Ethereum blockchain...'); terminal.current?.println('(the contract is very big. this may take a while)'); terminal.current?.newline(); const initialState = await gameStateDownloader.download(contractsAPI, persistentChunkStore); const possibleHomes = await persistentChunkStore.getHomeLocations(); terminal.current?.println(''); terminal.current?.println('Building Index...'); await persistentChunkStore.saveTouchedPlanetIds(initialState.allTouchedPlanetIds); await persistentChunkStore.saveRevealedCoords(initialState.allRevealedCoords); const knownArtifacts: Map = new Map(); for (let i = 0; i < initialState.loadedPlanets.length; i++) { const planet = initialState.touchedAndLocatedPlanets.get(initialState.loadedPlanets[i]); if (!planet) { continue; } planet.heldArtifactIds = initialState.heldArtifacts[i].map((a) => a.id); for (const heldArtifact of initialState.heldArtifacts[i]) { knownArtifacts.set(heldArtifact.id, heldArtifact); } } for (const myArtifact of initialState.myArtifacts) { knownArtifacts.set(myArtifact.id, myArtifact); } for (const artifact of initialState.artifactsOnVoyages) { knownArtifacts.set(artifact.id, artifact); } // figure out what's my home planet let homeLocation: WorldLocation | undefined = undefined; for (const loc of possibleHomes) { if (initialState.allTouchedPlanetIds.includes(loc.hash)) { homeLocation = loc; await persistentChunkStore.confirmHomeLocation(loc); break; } } const hashConfig: HashConfig = { planetHashKey: initialState.contractConstants.PLANETHASH_KEY, spaceTypeKey: initialState.contractConstants.SPACETYPE_KEY, biomebaseKey: initialState.contractConstants.BIOMEBASE_KEY, perlinLengthScale: initialState.contractConstants.PERLIN_LENGTH_SCALE, perlinMirrorX: initialState.contractConstants.PERLIN_MIRROR_X, perlinMirrorY: initialState.contractConstants.PERLIN_MIRROR_Y, planetRarity: initialState.contractConstants.PLANET_RARITY, }; const useMockHash = initialState.contractConstants.DISABLE_ZK_CHECKS; const snarkHelper = SnarkArgsHelper.create(hashConfig, terminal, useMockHash); const gameManager = new GameManager( terminal, account, initialState.players, initialState.touchedAndLocatedPlanets, new Set(Array.from(initialState.allTouchedPlanetIds)), initialState.revealedCoordsMap, initialState.claimedCoordsMap ? initialState.claimedCoordsMap : new Map(), initialState.worldRadius, initialState.arrivals, initialState.planetVoyageIdMap, contractsAPI, initialState.contractConstants, persistentChunkStore, snarkHelper, homeLocation, useMockHash, knownArtifacts, connection, initialState.paused ); gameManager.setPlayerTwitters(initialState.twitters); const config = { contractAddress, account: gameManager.getAccount(), }; pollSetting(config, Setting.AutoApproveNonPurchaseTransactions); persistentChunkStore.setDiagnosticUpdater(gameManager); contractsAPI.setDiagnosticUpdater(gameManager); // important that this happens AFTER we load the game state from the blockchain. Otherwise our // 'loading game state' contract calls will be competing with events from the blockchain that // are happening now, which makes no sense. contractsAPI.setupEventListeners(); // get twitter handles gameManager.refreshTwitters(); gameManager.listenForNewBlock(); // set up listeners: whenever ContractsAPI reports some game state update, do some logic gameManager.contractsAPI .on(ContractsAPIEvent.ArtifactUpdate, async (artifactId: ArtifactId) => { await gameManager.hardRefreshArtifact(artifactId); gameManager.emit(GameManagerEvent.ArtifactUpdate, artifactId); }) .on( ContractsAPIEvent.PlanetTransferred, async (planetId: LocationId, newOwner: EthAddress) => { await gameManager.hardRefreshPlanet(planetId); const planetAfter = gameManager.getPlanetWithId(planetId); if (planetAfter && newOwner === gameManager.account) { NotificationManager.getInstance().receivedPlanet(planetAfter); } } ) .on(ContractsAPIEvent.PlayerUpdate, async (playerId: EthAddress) => { await gameManager.hardRefreshPlayer(playerId); }) .on(ContractsAPIEvent.PauseStateChanged, async (paused: boolean) => { gameManager.paused = paused; gameManager.paused$.publish(paused); }) .on(ContractsAPIEvent.PlanetUpdate, async (planetId: LocationId) => { // don't reload planets that you don't have in your map. once a planet // is in your map it will be loaded from the contract. const localPlanet = gameManager.entityStore.getPlanetWithId(planetId); if (localPlanet && isLocatable(localPlanet)) { await gameManager.hardRefreshPlanet(planetId); gameManager.emit(GameManagerEvent.PlanetUpdate); } }) .on( ContractsAPIEvent.ArrivalQueued, async (_arrivalId: VoyageId, fromId: LocationId, toId: LocationId) => { // only reload planets if the toPlanet is in the map const localToPlanet = gameManager.entityStore.getPlanetWithId(toId); if (localToPlanet && isLocatable(localToPlanet)) { await gameManager.bulkHardRefreshPlanets([fromId, toId]); gameManager.emit(GameManagerEvent.PlanetUpdate); } } ) .on( ContractsAPIEvent.LocationRevealed, async (planetId: LocationId, _revealer: EthAddress) => { // TODO: hook notifs or emit event to UI if you want await gameManager.hardRefreshPlanet(planetId); gameManager.emit(GameManagerEvent.PlanetUpdate); } ) .on(ContractsAPIEvent.TxQueued, (tx: Transaction) => { gameManager.entityStore.onTxIntent(tx); }) .on(ContractsAPIEvent.TxSubmitted, (tx: Transaction) => { gameManager.persistentChunkStore.onEthTxSubmit(tx); gameManager.onTxSubmit(tx); }) .on(ContractsAPIEvent.TxConfirmed, async (tx: Transaction) => { if (!tx.hash) return; // this should never happen gameManager.persistentChunkStore.onEthTxComplete(tx.hash); if (isUnconfirmedRevealTx(tx)) { await gameManager.hardRefreshPlanet(tx.intent.locationId); } else if (isUnconfirmedInitTx(tx)) { terminal.current?.println('Loading Home Planet from Blockchain...'); const retries = 5; for (let i = 0; i < retries; i++) { const planet = await gameManager.contractsAPI.getPlanetById(tx.intent.locationId); if (planet) { break; } else if (i === retries - 1) { console.error("couldn't load player's home planet"); } else { await delay(2000); } } await gameManager.hardRefreshPlanet(tx.intent.locationId); // mining manager should be initialized already via joinGame, but just in case... gameManager.initMiningManager(tx.intent.location.coords, 4); } else if (isUnconfirmedMoveTx(tx)) { const promises = [gameManager.bulkHardRefreshPlanets([tx.intent.from, tx.intent.to])]; if (tx.intent.artifact) { promises.push(gameManager.hardRefreshArtifact(tx.intent.artifact)); } await Promise.all(promises); } else if (isUnconfirmedUpgradeTx(tx)) { await gameManager.hardRefreshPlanet(tx.intent.locationId); } else if (isUnconfirmedBuyHatTx(tx)) { await gameManager.hardRefreshPlanet(tx.intent.locationId); } else if (isUnconfirmedInitTx(tx)) { await gameManager.hardRefreshPlanet(tx.intent.locationId); } else if (isUnconfirmedFindArtifactTx(tx)) { await gameManager.hardRefreshPlanet(tx.intent.planetId); } else if (isUnconfirmedDepositArtifactTx(tx)) { await Promise.all([ gameManager.hardRefreshPlanet(tx.intent.locationId), gameManager.hardRefreshArtifact(tx.intent.artifactId), ]); } else if (isUnconfirmedWithdrawArtifactTx(tx)) { await Promise.all([ await gameManager.hardRefreshPlanet(tx.intent.locationId), await gameManager.hardRefreshArtifact(tx.intent.artifactId), ]); } else if (isUnconfirmedProspectPlanetTx(tx)) { await gameManager.softRefreshPlanet(tx.intent.planetId); } else if (isUnconfirmedActivateArtifactTx(tx)) { await Promise.all([ gameManager.hardRefreshPlanet(tx.intent.locationId), gameManager.hardRefreshArtifact(tx.intent.artifactId), ]); } else if (isUnconfirmedDeactivateArtifactTx(tx)) { await Promise.all([ gameManager.hardRefreshPlanet(tx.intent.locationId), gameManager.hardRefreshArtifact(tx.intent.artifactId), ]); } else if (isUnconfirmedWithdrawSilverTx(tx)) { await gameManager.softRefreshPlanet(tx.intent.locationId); } else if (isUnconfirmedCapturePlanetTx(tx)) { await Promise.all([ gameManager.hardRefreshPlayer(gameManager.getAccount()), gameManager.hardRefreshPlanet(tx.intent.locationId), ]); } else if (isUnconfirmedInvadePlanetTx(tx)) { await Promise.all([ gameManager.hardRefreshPlayer(gameManager.getAccount()), gameManager.hardRefreshPlanet(tx.intent.locationId), ]); } gameManager.entityStore.clearUnconfirmedTxIntent(tx); gameManager.onTxConfirmed(tx); }) .on(ContractsAPIEvent.TxErrored, async (tx: Transaction) => { gameManager.entityStore.clearUnconfirmedTxIntent(tx); if (tx.hash) { gameManager.persistentChunkStore.onEthTxComplete(tx.hash); } gameManager.onTxReverted(tx); }) .on(ContractsAPIEvent.TxCancelled, async (tx: Transaction) => { gameManager.onTxCancelled(tx); }) .on(ContractsAPIEvent.RadiusUpdated, async () => { const newRadius = await gameManager.contractsAPI.getWorldRadius(); gameManager.setRadius(newRadius); }); const unconfirmedTxs = await persistentChunkStore.getUnconfirmedSubmittedEthTxs(); const confirmationQueue = new ThrottledConcurrentQueue({ invocationIntervalMs: 1000, maxInvocationsPerIntervalMs: 10, maxConcurrency: 1, }); for (const unconfirmedTx of unconfirmedTxs) { confirmationQueue.add(async () => { const tx = gameManager.contractsAPI.txExecutor.waitForTransaction(unconfirmedTx); gameManager.contractsAPI.emitTransactionEvents(tx); return tx.confirmedPromise; }); } // we only want to initialize the mining manager if the player has already joined the game // if they haven't, we'll do this once the player has joined the game if (!!homeLocation && initialState.players.has(account as string)) { gameManager.initMiningManager(homeLocation.coords); } return gameManager; } private async hardRefreshPlayer(address?: EthAddress): Promise { if (!address) return; const playerFromBlockchain = await this.contractsAPI.getPlayerById(address); if (!playerFromBlockchain) return; const localPlayer = this.getPlayer(address); if (localPlayer?.twitter) { playerFromBlockchain.twitter = localPlayer.twitter; } this.players.set(address, playerFromBlockchain); this.playersUpdated$.publish(); } // Dirty hack for only refreshing properties on a planet and nothing else private async softRefreshPlanet(planetId: LocationId): Promise { const planet = await this.contractsAPI.getPlanetById(planetId); if (!planet) return; this.entityStore.replacePlanetFromContractData(planet); } public async hardRefreshPlanet(planetId: LocationId): Promise { const planet = await this.contractsAPI.getPlanetById(planetId); if (!planet) return; const arrivals = await this.contractsAPI.getArrivalsForPlanet(planetId); const artifactsOnPlanets = await this.contractsAPI.bulkGetArtifactsOnPlanets([planetId]); const artifactsOnPlanet = artifactsOnPlanets[0]; const revealedCoords = await this.contractsAPI.getRevealedCoordsByIdIfExists(planetId); let revealedLocation: RevealedLocation | undefined; let claimedCoords: ClaimedCoords | undefined; if (revealedCoords) { revealedLocation = { ...this.locationFromCoords(revealedCoords), revealer: revealedCoords.revealer, }; } this.entityStore.replacePlanetFromContractData( planet, arrivals, artifactsOnPlanet.map((a) => a.id), revealedLocation, claimedCoords?.revealer ); // it's important that we reload the artifacts that are on the planet after the move // completes because this move could have been a photoid canon move. one of the side // effects of this type of move is that the active photoid canon deactivates upon a move // meaning we need to reload its data from the blockchain. artifactsOnPlanet.forEach((a) => this.entityStore.replaceArtifactFromContractData(a)); } private async bulkHardRefreshPlanets(planetIds: LocationId[]): Promise { const planetVoyageMap: Map = new Map(); const allVoyages = await this.contractsAPI.getAllArrivals(planetIds); const planetsToUpdateMap = await this.contractsAPI.bulkGetPlanets(planetIds); const artifactsOnPlanets = await this.contractsAPI.bulkGetArtifactsOnPlanets(planetIds); planetsToUpdateMap.forEach((planet, locId) => { if (planetsToUpdateMap.has(locId)) { planetVoyageMap.set(locId, []); } }); for (const voyage of allVoyages) { const voyagesForToPlanet = planetVoyageMap.get(voyage.toPlanet); if (voyagesForToPlanet) { voyagesForToPlanet.push(voyage); planetVoyageMap.set(voyage.toPlanet, voyagesForToPlanet); } } for (let i = 0; i < planetIds.length; i++) { const planetId = planetIds[i]; const planet = planetsToUpdateMap.get(planetId); // This shouldn't really happen, but we are better off being safe - opposed to throwing if (!planet) { continue; } const voyagesForPlanet = planetVoyageMap.get(planet.locationId); if (voyagesForPlanet) { this.entityStore.replacePlanetFromContractData( planet, voyagesForPlanet, artifactsOnPlanets[i].map((a) => a.id) ); } } for (const artifacts of artifactsOnPlanets) { this.entityStore.replaceArtifactsFromContractData(artifacts); } } public async hardRefreshArtifact(artifactId: ArtifactId): Promise { const artifact = await this.contractsAPI.getArtifactById(artifactId); if (!artifact) return; this.entityStore.replaceArtifactFromContractData(artifact); } private onTxSubmit(tx: Transaction): void { this.terminal.current?.print(`${tx.intent.methodName} transaction (`, TerminalTextStyle.Blue); this.terminal.current?.printLink( `${tx.hash?.slice(0, 6) ?? ''}`, () => { window.open(`${BLOCK_EXPLORER_URL}/${tx.hash ?? ''}`); }, TerminalTextStyle.White ); this.terminal.current?.println(`) submitted`, TerminalTextStyle.Blue); } private onTxConfirmed(tx: Transaction) { this.terminal.current?.print(`${tx.intent.methodName} transaction (`, TerminalTextStyle.Green); this.terminal.current?.printLink( `${tx.hash?.slice(0, 6) ?? ''}`, () => { window.open(`${BLOCK_EXPLORER_URL}/${tx.hash ?? ''}`); }, TerminalTextStyle.White ); this.terminal.current?.println(`) confirmed`, TerminalTextStyle.Green); } private onTxReverted(tx: Transaction) { this.terminal.current?.print(`${tx.intent.methodName} transaction (`, TerminalTextStyle.Red); this.terminal.current?.printLink( `${tx.hash?.slice(0, 6) ?? ''}`, () => { window.open(`${BLOCK_EXPLORER_URL}/${tx.hash ?? ''}`); }, TerminalTextStyle.White ); this.terminal.current?.println(`) reverted`, TerminalTextStyle.Red); } private onTxCancelled(tx: Transaction) { this.entityStore.clearUnconfirmedTxIntent(tx); this.terminal.current?.print(`${tx.intent.methodName} transaction (`, TerminalTextStyle.Red); this.terminal.current?.printLink( `${tx.hash?.slice(0, 6) ?? ''}`, () => { window.open(`${BLOCK_EXPLORER_URL}/${tx.hash ?? ''}`); }, TerminalTextStyle.White ); this.terminal.current?.println(`) cancelled`, TerminalTextStyle.Red); } /** * Gets the address of the player logged into this game manager. */ public getAccount(): EthAddress | undefined { return this.account; } /** * Get the thing that handles contract interaction. */ public getContractAPI(): ContractsAPI { return this.contractsAPI; } /** * Gets the address of the `DarkForest` contract, which is the 'backend' of the game. */ public getContractAddress(): EthAddress { return this.contractsAPI.getContractAddress(); } /** * Gets the twitter handle of the given ethereum account which is associated * with Dark Forest. */ public getTwitter(address: EthAddress | undefined): string | undefined { let myAddress; if (!address) myAddress = this.getAccount(); else myAddress = address; if (!myAddress) { return undefined; } const twitter = this.players.get(myAddress)?.twitter; return twitter; } /** * The game ends at a particular time in the future - get this time measured * in seconds from the epoch. */ public getEndTimeSeconds(): number { return this.endTimeSeconds; } /** * Dark Forest tokens can only be minted up to a certain time - get this time measured in seconds from epoch. */ public getTokenMintEndTimeSeconds(): number { return this.contractConstants.TOKEN_MINT_END_SECONDS; } /** * Gets the rarity of planets in the universe */ public getPlanetRarity(): number { return this.contractConstants.PLANET_RARITY; } /** * returns timestamp (seconds) that planet will reach percent% of energycap * time may be in the past */ public getEnergyCurveAtPercent(planet: Planet, percent: number): number { return this.entityStore.getEnergyCurveAtPercent(planet, percent); } /** * returns timestamp (seconds) that planet will reach percent% of silcap if * doesn't produce silver, returns undefined if already over percent% of silcap, */ public getSilverCurveAtPercent(planet: Planet, percent: number): number | undefined { return this.entityStore.getSilverCurveAtPercent(planet, percent); } /** * Returns the upgrade that would be applied to a planet given a particular * upgrade branch (defense, range, speed) and level of upgrade. */ public getUpgrade(branch: number, level: number): Upgrade { return this.contractConstants.upgrades[branch][level]; } /** * Gets a list of all the players in the game (not just the ones you've * encounterd) */ public getAllPlayers(): Player[] { return Array.from(this.players.values()); } /** * Gets either the given player, or if no address was provided, gets the player that is logged * this client. */ public getPlayer(address?: EthAddress): Player | undefined { address = address || this.account; if (!address) { return undefined; } return this.players.get(address); } /** * Gets all the map chunks that this client is aware of. Chunks may have come from * mining, or from importing map data. */ public getExploredChunks(): Iterable { return this.persistentChunkStore.allChunks(); } /** * Gets the ids of all the planets that are both within the given bounding box (defined by its bottom * left coordinate, width, and height) in the world and of a level that was passed in via the * `planetLevels` parameter. */ public getPlanetsInWorldRectangle( worldX: number, worldY: number, worldWidth: number, worldHeight: number, levels: number[], planetLevelToRadii: Map, updateIfStale = true ): LocatablePlanet[] { return this.entityStore.getPlanetsInWorldRectangle( worldX, worldY, worldWidth, worldHeight, levels, planetLevelToRadii, updateIfStale ); } /** * Returns whether or not the current round has ended. */ public isRoundOver(): boolean { return Date.now() / 1000 > this.getTokenMintEndTimeSeconds(); } /** * Gets the radius of the playable area of the universe. */ public getWorldRadius(): number { return this.worldRadius; } /** * Gets the total amount of silver that lives on a planet that somebody owns. */ public getWorldSilver(): number { return this.getAllOwnedPlanets().reduce( (totalSoFar: number, nextPlanet: Planet) => totalSoFar + nextPlanet.silver, 0 ); } /** * Gets the total amount of energy that lives on a planet that somebody owns. */ public getUniverseTotalEnergy(): number { return this.getAllOwnedPlanets().reduce( (totalSoFar: number, nextPlanet: Planet) => totalSoFar + nextPlanet.energy, 0 ); } /** * Gets the total amount of silver that lives on planets that the given player owns. */ public getSilverOfPlayer(player: EthAddress): number { return this.getAllOwnedPlanets() .filter((planet) => planet.owner === player) .reduce((totalSoFar: number, nextPlanet: Planet) => totalSoFar + nextPlanet.silver, 0); } /** * Gets the total amount of energy that lives on planets that the given player owns. */ public getEnergyOfPlayer(player: EthAddress): number { return this.getAllOwnedPlanets() .filter((planet) => planet.owner === player) .reduce((totalSoFar: number, nextPlanet: Planet) => totalSoFar + nextPlanet.energy, 0); } public getPlayerScore(addr: EthAddress): number | undefined { const player = this.players.get(addr); return player?.score; } public getPlayerSpaceJunk(addr: EthAddress): number | undefined { const player = this.players.get(addr); return player?.spaceJunk; } public getPlayerSpaceJunkLimit(addr: EthAddress): number | undefined { const player = this.players.get(addr); return player?.spaceJunkLimit; } public getDefaultSpaceJunkForPlanetLevel(level: number) { return this.contractConstants.PLANET_LEVEL_JUNK[level]; } private initMiningManager(homeCoords: WorldCoords, cores?: number): void { if (this.minerManager) return; const myPattern: MiningPattern = new SpiralPattern(homeCoords, MIN_CHUNK_SIZE); this.minerManager = MinerManager.create( this.persistentChunkStore, myPattern, this.worldRadius, this.planetRarity, this.hashConfig, this.useMockHash ); const config = { contractAddress: this.getContractAddress(), account: this.account, }; this.minerManager.setCores(cores || getNumberSetting(config, Setting.MiningCores)); this.minerManager.on( MinerManagerEvent.DiscoveredNewChunk, (chunk: Chunk, miningTimeMillis: number) => { this.addNewChunk(chunk); this.hashRate = chunk.chunkFootprint.sideLength ** 2 / (miningTimeMillis / 1000); this.emit(GameManagerEvent.DiscoveredNewChunk, chunk); } ); const isMining = getBooleanSetting(config, Setting.IsMining); if (isMining) { this.minerManager.startExplore(); } } /** * Sets the mining pattern of the miner. This kills the old miner and starts this one. */ setMiningPattern(pattern: MiningPattern): void { if (this.minerManager) { this.minerManager.setMiningPattern(pattern); } } /** * Gets the mining pattern that the miner is currently using. */ getMiningPattern(): MiningPattern | undefined { if (this.minerManager) return this.minerManager.getMiningPattern(); else return undefined; } /** * Set the amount of cores to mine the universe with. More cores equals faster! */ setMinerCores(nCores: number): void { const config = { contractAddress: this.getContractAddress(), account: this.account, }; setSetting(config, Setting.MiningCores, nCores + ''); } /** * Whether or not the miner is currently exploring space. */ isMining(): boolean { return this.minerManager?.isMining() || false; } /** * Changes the amount of move snark proofs that are cached. */ setSnarkCacheSize(size: number): void { this.snarkHelper.setSnarkCacheSize(size); } /** * Gets the rectangle bounding the chunk that the miner is currently in the process * of hashing. */ getCurrentlyExploringChunk(): Rectangle | undefined { if (this.minerManager) { return this.minerManager.getCurrentlyExploringChunk(); } return undefined; } /** * Whether or not this client has successfully found and landed on a home planet. */ hasJoinedGame(): boolean { return this.players.has(this.account as string); } /** * Returns info about the next time you can broadcast coordinates */ getNextRevealCountdownInfo(): RevealCountdownInfo { if (!this.account) { throw new Error('no account set'); } const myLastRevealTimestamp = this.players.get(this.account)?.lastRevealTimestamp; return { myLastRevealTimestamp: myLastRevealTimestamp || undefined, currentlyRevealing: this.entityStore.transactions.hasTransaction(isUnconfirmedRevealTx), revealCooldownTime: this.contractConstants.LOCATION_REVEAL_COOLDOWN, }; } /** * gets both deposited artifacts that are on planets i own as well as artifacts i own */ getMyArtifacts(): Artifact[] { if (!this.account) return []; const ownedByMe = this.entityStore.getArtifactsOwnedBy(this.account); const onPlanetsOwnedByMe = this.entityStore .getArtifactsOnPlanetsOwnedBy(this.account) // filter out space ships because they always show up // in the `ownedByMe` array. .filter((a) => !isSpaceShip(a.artifactType)); return [...ownedByMe, ...onPlanetsOwnedByMe]; } /** * Gets the planet that is located at the given coordinates. Returns undefined if not a valid * location or if no planet exists at location. If the planet needs to be updated (because * some time has passed since we last updated the planet), then updates that planet first. */ getPlanetWithCoords(coords: WorldCoords): LocatablePlanet | undefined { return this.entityStore.getPlanetWithCoords(coords); } /** * Gets the planet with the given hash. Returns undefined if the planet is neither in the contract * nor has been discovered locally. If the planet needs to be updated (because some time has * passed since we last updated the planet), then updates that planet first. */ getPlanetWithId(planetId: LocationId | undefined): Planet | undefined { return planetId && this.entityStore.getPlanetWithId(planetId); } /** * Gets a list of planets in the client's memory with the given ids. If a planet with the given id * doesn't exist, no entry for that planet will be returned in the result. */ getPlanetsWithIds(planetId: LocationId[]): Planet[] { return planetId.map((id) => this.getPlanetWithId(id)).filter((p) => !!p) as Planet[]; } getStalePlanetWithId(planetId: LocationId): Planet | undefined { return this.entityStore.getPlanetWithId(planetId, false); } /** * Get the score of the currently logged-in account. */ getMyScore(): number | undefined { if (!this.account) { return undefined; } const player = this.players.get(this.account); return player?.score; } /** * Gets the artifact with the given id. Null if no artifact with id exists. */ getArtifactWithId(artifactId?: ArtifactId): Artifact | undefined { return this.entityStore.getArtifactById(artifactId); } /** * Gets the artifacts with the given ids, including ones we know exist but haven't been loaded, * represented by `undefined`. */ getArtifactsWithIds(artifactIds: ArtifactId[] = []): Array { return artifactIds.map((id) => this.getArtifactWithId(id)); } /** * Gets the level of the given planet. Returns undefined if the planet does not exist. Does * NOT update the planet if the planet is stale, which means this function is fast. */ getPlanetLevel(planetId: LocationId): PlanetLevel | undefined { return this.entityStore.getPlanetLevel(planetId); } /** * Gets the location of the given planet. Returns undefined if the planet does not exist, or if * we do not know the location of this planet NOT update the planet if the planet is stale, * which means this function is fast. */ getLocationOfPlanet(planetId: LocationId): WorldLocation | undefined { return this.entityStore.getLocationOfPlanet(planetId); } /** * Gets all voyages that have not completed. */ getAllVoyages(): QueuedArrival[] { return this.entityStore.getAllVoyages(); } /** * Gets all planets. This means all planets that are in the contract, and also all * planets that have been mined locally. Does not update planets if they are stale. * NOT PERFORMANT - for scripting only. */ getAllPlanets(): Iterable { return this.entityStore.getAllPlanets(); } /** * Gets a list of planets that have an owner. */ getAllOwnedPlanets(): Planet[] { return this.entityStore.getAllOwnedPlanets(); } /** * Gets a list of the planets that the player logged into this `GameManager` owns. */ getMyPlanets(): Planet[] { return this.getAllOwnedPlanets().filter((planet) => planet.owner === this.account); } /** * Gets a map of all location IDs whose coords have been publically revealed */ getRevealedLocations(): Map { return this.entityStore.getRevealedLocations(); } /** * Gets a map of all location IDs which have been claimed. */ getClaimedLocations(): Map { return this.entityStore.getClaimedLocations(); } /** * Each coordinate lives in a particular type of space, determined by a smooth random * function called 'perlin noise. */ spaceTypeFromPerlin(perlin: number): SpaceType { return this.entityStore.spaceTypeFromPerlin(perlin); } /** * Gets the amount of hashes per second that the miner manager is calculating. */ getHashesPerSec(): number { return this.hashRate; } /** * Signs the given twitter handle with the private key of the current user. Used to * verify that the person who owns the Dark Forest account was the one that attempted * to link a twitter to their account. */ async getSignedTwitter(twitter: string): Promise { return this.ethConnection.signMessage(twitter); } /** * Gets the private key of the burner wallet used by this account. */ getPrivateKey(): string | undefined { return this.ethConnection.getPrivateKey(); } /** * Gets the balance of the account measured in Eth (i.e. in full units of the chain). */ getMyBalanceEth(): number { if (!this.account) return 0; return weiToEth(this.getMyBalance()); } /** * Gets the balance of the account */ getMyBalance(): BigNumber { return this.ethConnection.getMyBalance() || BigNumber.from('0'); } /** * Returns the monomitter which publishes events whenever the player's balance changes. */ getMyBalance$(): Monomitter { return this.ethConnection.myBalance$; } /** * Gets all moves that this client has queued to be uploaded to the contract, but * have not been successfully confirmed yet. */ getUnconfirmedMoves(): Transaction[] { return this.entityStore.transactions.getTransactions(isUnconfirmedMoveTx); } /** * Gets all upgrades that this client has queued to be uploaded to the contract, but * have not been successfully confirmed yet. */ getUnconfirmedUpgrades(): Transaction[] { return this.entityStore.transactions.getTransactions(isUnconfirmedUpgradeTx); } getUnconfirmedWormholeActivations(): Transaction[] { return this.entityStore.transactions .getTransactions(isUnconfirmedActivateArtifactTx) .filter((tx) => tx.intent.wormholeTo !== undefined); } /** * Gets the location of your home planet. */ getHomeCoords(): WorldCoords | undefined { if (!this.homeLocation) return undefined; return { x: this.homeLocation.coords.x, y: this.homeLocation.coords.y, }; } /** * Gets the hash of the location of your home planet. */ getHomeHash(): LocationId | undefined { return this.homeLocation?.hash; } /** * Gets the HASH CONFIG */ getHashConfig(): HashConfig { return { ...this.hashConfig }; } /** * Whether or not the given rectangle has been mined. */ hasMinedChunk(chunkLocation: Rectangle): boolean { return this.persistentChunkStore.hasMinedChunk(chunkLocation); } getChunk(chunkFootprint: Rectangle): Chunk | undefined { return this.persistentChunkStore.getChunkByFootprint(chunkFootprint); } getChunkStore(): PersistentChunkStore { return this.persistentChunkStore; } /** * The perlin value at each coordinate determines the space type. There are four space * types, which means there are four ranges on the number line that correspond to * each space type. This function returns the boundary values between each of these * four ranges: `PERLIN_THRESHOLD_1`, `PERLIN_THRESHOLD_2`, `PERLIN_THRESHOLD_3`. */ getPerlinThresholds(): [number, number, number] { return [ this.contractConstants.PERLIN_THRESHOLD_1, this.contractConstants.PERLIN_THRESHOLD_2, this.contractConstants.PERLIN_THRESHOLD_3, ]; } /** * Starts the miner. */ startExplore(): void { if (this.minerManager) { const config = { contractAddress: this.getContractAddress(), account: this.account, }; setBooleanSetting(config, Setting.IsMining, true); this.minerManager.startExplore(); } } /** * Stops the miner. */ stopExplore(): void { if (this.minerManager) { const config = { contractAddress: this.getContractAddress(), account: this.account, }; setBooleanSetting(config, Setting.IsMining, false); this.hashRate = 0; this.minerManager.stopExplore(); } } private setRadius(worldRadius: number) { this.worldRadius = worldRadius; if (this.minerManager) { this.minerManager.setRadius(this.worldRadius); } } private async refreshTwitters(): Promise { const addressTwitters = await getAllTwitters(); this.setPlayerTwitters(addressTwitters); } private setPlayerTwitters(twitters: AddressTwitterMap): void { for (const [address, player] of this.players.entries()) { const newPlayerTwitter = twitters[address]; player.twitter = newPlayerTwitter; } this.playersUpdated$.publish(); } /** * Once you have posted the verification tweet - complete the twitter-account-linking * process by telling the Dark Forest webserver to look at that tweet. */ async submitVerifyTwitter(twitter: string): Promise { if (!this.account) return Promise.resolve(false); const success = await verifyTwitterHandle( await this.ethConnection.signMessageObject({ twitter }) ); await this.refreshTwitters(); return success; } private checkGameHasEnded(): boolean { if (Date.now() / 1000 > this.endTimeSeconds) { this.terminal.current?.println('[ERROR] Game has ended.'); return true; } return false; } /** * Gets the timestamp (ms) of the next time that we can broadcast the coordinates of a planet. */ public getNextBroadcastAvailableTimestamp() { return Date.now() + this.timeUntilNextBroadcastAvailable(); } /** * Gets the amount of time (ms) until the next time the current player can broadcast a planet. */ public timeUntilNextBroadcastAvailable() { if (!this.account) { throw new Error('no account set'); } const myLastRevealTimestamp = this.players.get(this.account)?.lastRevealTimestamp; return timeUntilNextBroadcastAvailable( myLastRevealTimestamp, this.contractConstants.LOCATION_REVEAL_COOLDOWN ); } /** * Gets the timestamp (ms) of the next time that we can claim a planet. */ public getNextClaimAvailableTimestamp() { if (!this.account) { throw new Error('no account set'); } const myLastClaimTimestamp = this.players.get(this.account)?.lastClaimTimestamp; if (!myLastClaimTimestamp) { return Date.now(); } if (!this.contractConstants.CLAIM_PLANET_COOLDOWN) { return 0; } // both the variables in the next line are denominated in seconds return (myLastClaimTimestamp + this.contractConstants.CLAIM_PLANET_COOLDOWN) * 1000; } public getCaptureZones(): Set { return this.captureZoneGenerator?.getZones() || new Set(); } /** * Reveals a planet's location on-chain. */ public async revealLocation(planetId: LocationId): Promise> { try { if (!this.account) { throw new Error('no account set'); } const planet = this.entityStore.getPlanetWithId(planetId); if (!planet) { throw new Error("you can't reveal a planet you haven't discovered"); } if (!isLocatable(planet)) { throw new Error("you can't reveal a planet whose coordinates you don't know"); } if (planet.coordsRevealed) { throw new Error("this planet's location is already revealed"); } if (planet.transactions?.hasTransaction(isUnconfirmedRevealTx)) { throw new Error("you're already revealing this planet's location"); } if (this.entityStore.transactions.hasTransaction(isUnconfirmedRevealTx)) { throw new Error("you're already broadcasting coordinates"); } const myLastRevealTimestamp = this.players.get(this.account)?.lastRevealTimestamp; if (myLastRevealTimestamp && Date.now() < this.getNextBroadcastAvailableTimestamp()) { throw new Error('still on cooldown for broadcasting'); } // this is shitty. used for the popup window localStorage.setItem(`${this.getAccount()?.toLowerCase()}-revealLocationId`, planetId); const getArgs = async () => { const revealArgs = await this.snarkHelper.getRevealArgs( planet.location.coords.x, planet.location.coords.y ); this.terminal.current?.println( 'REVEAL: calculated SNARK with args:', TerminalTextStyle.Sub ); this.terminal.current?.println( JSON.stringify(hexifyBigIntNestedArray(revealArgs.slice(0, 3))), TerminalTextStyle.Sub ); this.terminal.current?.newline(); return revealArgs; }; const txIntent: UnconfirmedReveal = { methodName: 'revealLocation', contract: this.contractsAPI.contract, locationId: planetId, location: planet.location, args: getArgs(), }; // Always await the submitTransaction so we can catch rejections const tx = await this.contractsAPI.submitTransaction(txIntent); return tx; } catch (e) { this.getNotificationsManager().txInitError('revealLocation', e.message); throw e; } } public async invadePlanet(locationId: LocationId) { try { if (!this.captureZoneGenerator) { throw new Error('Capture zones are not enabled in this game'); } const planet = this.entityStore.getPlanetWithId(locationId); if (!planet || !isLocatable(planet)) { throw new Error("you can't invade a planet you haven't discovered"); } if (planet.destroyed) { throw new Error("you can't invade destroyed planets"); } if (planet.invader !== EMPTY_ADDRESS) { throw new Error("you can't invade planets that have already been invaded"); } if (planet.owner !== this.account) { throw new Error('you can only invade planets you own'); } if (!this.captureZoneGenerator.isInZone(planet.locationId)) { throw new Error("you can't invade planets that are not in a capture zone"); } localStorage.setItem(`${this.getAccount()?.toLowerCase()}-invadePlanet`, locationId); const getArgs = async () => { const revealArgs = await this.snarkHelper.getRevealArgs( planet.location.coords.x, planet.location.coords.y ); this.terminal.current?.println( 'REVEAL: calculated SNARK with args:', TerminalTextStyle.Sub ); this.terminal.current?.println( JSON.stringify(hexifyBigIntNestedArray(revealArgs.slice(0, 3))), TerminalTextStyle.Sub ); this.terminal.current?.newline(); return revealArgs; }; const txIntent: UnconfirmedInvadePlanet = { methodName: 'invadePlanet', contract: this.contractsAPI.contract, locationId, args: getArgs(), }; const tx = await this.contractsAPI.submitTransaction(txIntent); return tx; } catch (e) { this.getNotificationsManager().txInitError('invadePlanet', e.message); throw e; } } public async capturePlanet(locationId: LocationId) { try { const planet = this.entityStore.getPlanetWithId(locationId); if (!planet) { throw new Error('planet is not loaded'); } if (planet.destroyed) { throw new Error("you can't capture destroyed planets"); } if (planet.capturer !== EMPTY_ADDRESS) { throw new Error("you can't capture planets that have already been captured"); } if (planet.owner !== this.account) { throw new Error('you can only capture planets you own'); } if (planet.energy < planet.energyCap * 0.8) { throw new Error('the planet needs >80% energy before capturing'); } if ( !planet.invadeStartBlock || this.ethConnection.getCurrentBlockNumber() < planet.invadeStartBlock + this.contractConstants.CAPTURE_ZONE_HOLD_BLOCKS_REQUIRED ) { throw new Error( `you need to hold a planet for ${this.contractConstants.CAPTURE_ZONE_HOLD_BLOCKS_REQUIRED} blocks before capturing` ); } localStorage.setItem(`${this.getAccount()?.toLowerCase()}-capturePlanet`, locationId); const txIntent: UnconfirmedCapturePlanet = { methodName: 'capturePlanet', contract: this.contractsAPI.contract, locationId, args: Promise.resolve([locationIdToDecStr(locationId)]), }; const tx = await this.contractsAPI.submitTransaction(txIntent); return tx; } catch (e) { this.getNotificationsManager().txInitError('capturePlanet', e.message); throw e; } } /** * Attempts to join the game. Should not be called once you've already joined. */ public async joinGame(beforeRetry: (e: Error) => Promise): Promise { try { if (this.checkGameHasEnded()) { throw new Error('game has ended'); } const planet = await this.findRandomHomePlanet(); this.homeLocation = planet.location; this.terminal.current?.println(''); this.terminal.current?.println(`Found Suitable Home Planet: ${getPlanetName(planet)} `); this.terminal.current?.println( `Its coordinates are: (${planet.location.coords.x}, ${planet.location.coords.y})` ); this.terminal.current?.println(''); await this.persistentChunkStore.addHomeLocation(planet.location); const getArgs = async () => { const args = await this.snarkHelper.getInitArgs( planet.location.coords.x, planet.location.coords.y, Math.floor(Math.sqrt(planet.location.coords.x ** 2 + planet.location.coords.y ** 2)) + 1 // floor(sqrt(x^2 + y^2)) + 1 ); this.terminal.current?.println('INIT: calculated SNARK with args:', TerminalTextStyle.Sub); this.terminal.current?.println( JSON.stringify(hexifyBigIntNestedArray(args.slice(0, 3) as unknown as string[])), TerminalTextStyle.Sub ); this.terminal.current?.newline(); return args; }; const txIntent: UnconfirmedInit = { methodName: 'initializePlayer', contract: this.contractsAPI.contract, locationId: planet.location.hash, location: planet.location, args: getArgs(), }; this.terminal.current?.println('INIT: proving that planet exists', TerminalTextStyle.Sub); this.initMiningManager(planet.location.coords); // get an early start // if player initialization causes an error, give the caller an opportunity // to resolve that error. if the asynchronous `beforeRetry` function returns // true, retry initializing the player. if it returns false, or if the // `beforeRetry` is undefined, then don't retry and throw an exception. while (true) { try { const tx = await this.contractsAPI.submitTransaction(txIntent); await tx.confirmedPromise; break; } catch (e) { if (beforeRetry) { if (await beforeRetry(e)) { continue; } } else { throw e; } } } await this.getSpaceships(); await this.hardRefreshPlanet(planet.locationId); this.emit(GameManagerEvent.InitializedPlayer); } catch (e) { this.getNotificationsManager().txInitError('initializePlayer', e.message); throw e; } } private async getSpaceships() { if (!this.account || !this.homeLocation?.hash) return; const player = await this.contractsAPI.getPlayerById(this.account); if (player?.claimedShips) return; if (this.getGameObjects().isGettingSpaceships()) return; const tx = await this.contractsAPI.submitTransaction({ methodName: 'giveSpaceShips', contract: this.contractsAPI.contract, args: Promise.resolve(['0x' + this.homeLocation?.hash]), }); await tx.confirmedPromise; this.hardRefreshPlanet(this.homeLocation?.hash); } // this is slow, do not call in i.e. render/draw loop /** * * computes the WorldLocation object corresponding to a set of coordinates * very slow since it actually calculates the hash; do not use in render loop */ private locationFromCoords(coords: WorldCoords): WorldLocation { return { coords, hash: locationIdFromBigInt(this.planetHashMimc(coords.x, coords.y)), perlin: this.spaceTypePerlin(coords, true), biomebase: this.biomebasePerlin(coords, true), }; } /** * Initializes a new player's game to start at the given home planet. Must have already * initialized the player on the contract. */ async addAccount(coords: WorldCoords): Promise { const loc: WorldLocation = this.locationFromCoords(coords); await this.persistentChunkStore.addHomeLocation(loc); this.initMiningManager(coords); this.homeLocation = loc; return true; } private async findRandomHomePlanet(): Promise { return new Promise((resolve, reject) => { const initPerlinMin = this.contractConstants.INIT_PERLIN_MIN; const initPerlinMax = this.contractConstants.INIT_PERLIN_MAX; let minedChunksCount = 0; let x: number; let y: number; let d: number; let p: number; // if this.contractConstants.SPAWN_RIM_AREA is non-zero, then players must spawn in that // area, distributed evenly in the inner perimeter of the world let spawnInnerRadius = Math.sqrt( Math.max(Math.PI * this.worldRadius ** 2 - this.contractConstants.SPAWN_RIM_AREA, 0) / Math.PI ); if (this.contractConstants.SPAWN_RIM_AREA === 0) { spawnInnerRadius = 0; } do { // sample from square x = Math.random() * this.worldRadius * 2 - this.worldRadius; y = Math.random() * this.worldRadius * 2 - this.worldRadius; d = Math.sqrt(x ** 2 + y ** 2); p = this.spaceTypePerlin({ x, y }, false); } while ( p >= initPerlinMax || // keep searching if above or equal to the max p < initPerlinMin || // keep searching if below the minimum d >= this.worldRadius || // can't be out of bound d <= spawnInnerRadius // can't be inside spawn area ring ); // when setting up a new account in development mode, you can tell // the game where to start searching for planets using this query // string parameter. for example: // // ?searchCenter=2866,5627 // const params = new URLSearchParams(window.location.search); if (params.has('searchCenter')) { const parts = params.get('searchCenter')?.split(','); if (parts) { x = parseInt(parts[0], 10); y = parseInt(parts[1], 10); } } const pattern: MiningPattern = new SpiralPattern({ x, y }, MIN_CHUNK_SIZE); const chunkStore = new HomePlanetMinerChunkStore( initPerlinMin, initPerlinMax, this.hashConfig ); const homePlanetFinder = MinerManager.create( chunkStore, pattern, this.worldRadius, this.planetRarity, this.hashConfig, this.useMockHash ); this.terminal.current?.println(``); this.terminal.current?.println(`Initializing Home Planet Search...`); this.terminal.current?.println(``); this.terminal.current?.println(`Chunked explorer: start!`); this.terminal.current?.println( `Each chunk contains ${MIN_CHUNK_SIZE}x${MIN_CHUNK_SIZE} coordinates.` ); const percentSpawn = (1 / this.contractConstants.PLANET_RARITY) * 100; const printProgress = 8; this.terminal.current?.print(`Each coordinate has a`); this.terminal.current?.print(` ${percentSpawn}%`, TerminalTextStyle.Text); this.terminal.current?.print(` chance of spawning a planet.`); this.terminal.current?.println(''); this.terminal.current?.println( `Hashing first ${MIN_CHUNK_SIZE ** 2 * printProgress} potential home planets...` ); homePlanetFinder.on(MinerManagerEvent.DiscoveredNewChunk, (chunk: Chunk) => { chunkStore.addChunk(chunk); minedChunksCount++; if (minedChunksCount % printProgress === 0) { this.terminal.current?.println( `Hashed ${minedChunksCount * MIN_CHUNK_SIZE ** 2} potential home planets...` ); } for (const homePlanetLocation of chunk.planetLocations) { const planetPerlin = homePlanetLocation.perlin; const planetX = homePlanetLocation.coords.x; const planetY = homePlanetLocation.coords.y; const planetLevel = this.entityStore.planetLevelFromHexPerlin( homePlanetLocation.hash, homePlanetLocation.perlin ); const planetType = this.entityStore.planetTypeFromHexPerlin( homePlanetLocation.hash, homePlanetLocation.perlin ); const planet = this.getPlanetWithId(homePlanetLocation.hash); const distFromOrigin = Math.sqrt(planetX ** 2 + planetY ** 2); if ( planetPerlin < initPerlinMax && planetPerlin >= initPerlinMin && distFromOrigin < this.worldRadius && distFromOrigin > spawnInnerRadius && planetLevel === MIN_PLANET_LEVEL && planetType === PlanetType.PLANET && (!planet || !planet.isInContract) // init will fail if planet has been initialized in contract already ) { // valid home planet homePlanetFinder.stopExplore(); homePlanetFinder.destroy(); const homePlanet = this.getGameObjects().getPlanetWithLocation(homePlanetLocation); if (!homePlanet) { reject(new Error("Unable to create default planet for your home planet's location.")); } else { // can cast to `LocatablePlanet` because we know its location, as we just mined it. resolve(homePlanet as LocatablePlanet); } break; } } }); homePlanetFinder.startExplore(); }); } public async prospectPlanet( planetId: LocationId, bypassChecks = false ): Promise> { const planet = this.entityStore.getPlanetWithId(planetId); try { if (!planet || !isLocatable(planet)) { throw new Error("you can't prospect a planet you haven't discovered"); } if (!bypassChecks) { if (this.checkGameHasEnded()) throw new Error('game ended'); if (!planet) { throw new Error("you can't prospect a planet you haven't discovered"); } if (planet.owner !== this.getAccount()) { throw new Error("you can't prospect a planet you don't own"); } if (!isLocatable(planet)) { throw new Error("you don't know this planet's location"); } if (planet.prospectedBlockNumber !== undefined) { throw new Error('someone already prospected this planet'); } if (planet.transactions?.hasTransaction(isUnconfirmedProspectPlanetTx)) { throw new Error("you're already looking bro..."); } if (planet.planetType !== PlanetType.RUINS) { throw new Error("this planet doesn't have an artifact on it."); } } localStorage.setItem(`${this.getAccount()?.toLowerCase()}-prospectPlanet`, planetId); const txIntent: UnconfirmedProspectPlanet = { methodName: 'prospectPlanet', contract: this.contractsAPI.contract, planetId: planetId, args: Promise.resolve([locationIdToDecStr(planetId)]), }; const tx = await this.contractsAPI.submitTransaction(txIntent); tx.confirmedPromise.then(() => NotificationManager.getInstance().artifactProspected(planet as LocatablePlanet) ); return tx; } catch (e) { this.getNotificationsManager().txInitError('prospectPlanet', e.message); throw e; } } /** * Calls the contract to find an artifact on the given planet. */ public async findArtifact( planetId: LocationId, bypassChecks = false ): Promise> { const planet = this.entityStore.getPlanetWithId(planetId); try { if (!planet) { throw new Error("you can't find artifacts on a planet you haven't discovered"); } if (!isLocatable(planet)) { throw new Error("you don't know the biome of this planet"); } if (!bypassChecks) { if (this.checkGameHasEnded()) { throw new Error('game has ended'); } if (planet.owner !== this.getAccount()) { throw new Error("you can't find artifacts on planets you don't own"); } if (planet.hasTriedFindingArtifact) { throw new Error('someone already tried finding an artifact on this planet'); } if (planet.transactions?.hasTransaction(isUnconfirmedFindArtifactTx)) { throw new Error("you're already looking bro..."); } if (planet.planetType !== PlanetType.RUINS) { throw new Error("this planet doesn't have an artifact on it."); } } // this is shitty. used for the popup window localStorage.setItem(`${this.getAccount()?.toLowerCase()}-findArtifactOnPlanet`, planetId); const txIntent: UnconfirmedFindArtifact = { methodName: 'findArtifact', contract: this.contractsAPI.contract, planetId: planet.locationId, args: this.snarkHelper.getFindArtifactArgs( planet.location.coords.x, planet.location.coords.y ), }; const tx = await this.contractsAPI.submitTransaction(txIntent); tx.confirmedPromise .then(() => { return this.waitForPlanet(planet.locationId, ({ current }: Diff) => { return current.heldArtifactIds .map(this.getArtifactWithId.bind(this)) .find((a: Artifact) => a?.planetDiscoveredOn === planet.locationId) as Artifact; }).then((foundArtifact) => { if (!foundArtifact) throw new Error('Artifact not found?'); const notifManager = NotificationManager.getInstance(); notifManager.artifactFound(planet as LocatablePlanet, foundArtifact); }); }) .catch(console.log); return tx; } catch (e) { this.getNotificationsManager().txInitError('findArtifact', e.message); throw e; } } getContractConstants(): ContractConstants { return this.contractConstants; } /** * Submits a transaction to the blockchain to deposit an artifact on a given planet. * You must own the planet and you must own the artifact directly (can't be locked in contract) */ public async depositArtifact( locationId: LocationId, artifactId: ArtifactId ): Promise> { try { localStorage.setItem(`${this.getAccount()?.toLowerCase()}-depositPlanet`, locationId); localStorage.setItem(`${this.getAccount()?.toLowerCase()}-depositArtifact`, artifactId); if (this.checkGameHasEnded()) { const error = new Error('game has ended'); this.getNotificationsManager().txInitError('depositArtifact', error.message); throw error; } const txIntent: UnconfirmedDepositArtifact = { methodName: 'depositArtifact', contract: this.contractsAPI.contract, locationId, artifactId, args: Promise.resolve([locationIdToDecStr(locationId), artifactIdToDecStr(artifactId)]), }; const tx = await this.contractsAPI.submitTransaction(txIntent); tx.confirmedPromise.then(() => this.getGameObjects().updateArtifact(artifactId, (a) => (a.onPlanetId = locationId)) ); return tx; } catch (e) { this.getNotificationsManager().txInitError('depositArtifact', e.message); throw e; } } /** * Withdraws the artifact that is locked up on the given planet. */ public async withdrawArtifact( locationId: LocationId, artifactId: ArtifactId, bypassChecks = true ): Promise> { try { if (!bypassChecks) { if (this.checkGameHasEnded()) { throw new Error('game has ended'); } const planet = this.entityStore.getPlanetWithId(locationId); if (!planet) { throw new Error('tried to withdraw from unknown planet'); } if (!artifactId) { throw new Error('must supply an artifact id'); } } // this is shitty. used for the popup window localStorage.setItem(`${this.getAccount()?.toLowerCase()}-withdrawPlanet`, locationId); localStorage.setItem(`${this.getAccount()?.toLowerCase()}-withdrawArtifact`, artifactId); const txIntent: UnconfirmedWithdrawArtifact = { methodName: 'withdrawArtifact', contract: this.contractsAPI.contract, args: Promise.resolve([locationIdToDecStr(locationId), artifactIdToDecStr(artifactId)]), locationId, artifactId, }; this.terminal.current?.println( 'WITHDRAW_ARTIFACT: sending withdrawal to blockchain', TerminalTextStyle.Sub ); this.terminal.current?.newline(); const tx = await this.contractsAPI.submitTransaction(txIntent); tx.confirmedPromise.then(() => this.getGameObjects().updateArtifact(artifactId, (a) => (a.onPlanetId = undefined)) ); return tx; } catch (e) { this.getNotificationsManager().txInitError('withdrawArtifact', e.message); throw e; } } public async activateArtifact( locationId: LocationId, artifactId: ArtifactId, wormholeTo: LocationId | undefined, bypassChecks = false ): Promise> { try { if (this.checkGameHasEnded()) { throw new Error('game has ended'); } if (!bypassChecks) { const planet = this.entityStore.getPlanetWithId(locationId); if (this.checkGameHasEnded()) { throw new Error('game has ended'); } if (!planet) { throw new Error('tried to activate on an unknown planet'); } if (!artifactId) { throw new Error('must supply an artifact id'); } } localStorage.setItem(`${this.getAccount()?.toLowerCase()}-activatePlanet`, locationId); localStorage.setItem(`${this.getAccount()?.toLowerCase()}-activateArtifact`, artifactId); const txIntent: UnconfirmedActivateArtifact = { methodName: 'activateArtifact', contract: this.contractsAPI.contract, args: Promise.resolve([ locationIdToDecStr(locationId), artifactIdToDecStr(artifactId), wormholeTo ? locationIdToDecStr(wormholeTo) : '0', ]), locationId, artifactId, wormholeTo, }; // Always await the submitTransaction so we can catch rejections const tx = await this.contractsAPI.submitTransaction(txIntent); return tx; } catch (e) { this.getNotificationsManager().txInitError('activateArtifact', e.message); throw e; } } public async deactivateArtifact( locationId: LocationId, artifactId: ArtifactId, bypassChecks = false ): Promise> { try { if (!bypassChecks) { const planet = this.entityStore.getPlanetWithId(locationId); if (!planet) { throw new Error('tried to deactivate on an unknown planet'); } } localStorage.setItem(`${this.getAccount()?.toLowerCase()}-deactivatePlanet`, locationId); localStorage.setItem(`${this.getAccount()?.toLowerCase()}-deactivateArtifact`, artifactId); const txIntent: UnconfirmedDeactivateArtifact = { methodName: 'deactivateArtifact', contract: this.contractsAPI.contract, args: Promise.resolve([locationIdToDecStr(locationId)]), locationId, artifactId, }; // Always await the submitTransaction so we can catch rejections const tx = await this.contractsAPI.submitTransaction(txIntent); return tx; } catch (e) { this.getNotificationsManager().txInitError('deactivateArtifact', e.message); throw e; } } public async withdrawSilver( locationId: LocationId, amount: number, bypassChecks = false ): Promise> { try { if (!bypassChecks) { if (!this.account) throw new Error('no account'); if (this.checkGameHasEnded()) { throw new Error('game has ended'); } const planet = this.entityStore.getPlanetWithId(locationId); if (!planet) { throw new Error('tried to withdraw silver from an unknown planet'); } if (planet.planetType !== PlanetType.TRADING_POST) { throw new Error('can only withdraw silver from spacetime rips'); } if (planet.owner !== this.account) { throw new Error('can only withdraw silver from a planet you own'); } if (planet.transactions?.hasTransaction(isUnconfirmedWithdrawSilverTx)) { throw new Error('a withdraw silver action is already in progress for this planet'); } if (amount > planet.silver) { throw new Error('not enough silver to withdraw!'); } if (amount === 0) { throw new Error('must withdraw more than 0 silver!'); } if (planet.destroyed) { throw new Error("can't withdraw silver from a destroyed planet"); } } localStorage.setItem(`${this.getAccount()?.toLowerCase()}-withdrawSilverPlanet`, locationId); const txIntent: UnconfirmedWithdrawSilver = { methodName: 'withdrawSilver', contract: this.contractsAPI.contract, args: Promise.resolve([locationIdToDecStr(locationId), amount * CONTRACT_PRECISION]), locationId, amount, }; // Always await the submitTransaction so we can catch rejections const tx = await this.contractsAPI.submitTransaction(txIntent); return tx; } catch (e) { this.getNotificationsManager().txInitError('withdrawSilver', e.message); throw e; } } /** * We have two locations which planet state can live: on the server, and on the blockchain. We use * the blockchain for the 'physics' of the universe, and the webserver for optional 'add-on' * features, which are cryptographically secure, but live off-chain. * * This function loads the planet states which live on the server. Plays nicely with our * notifications system and sets the appropriate loading state values on the planet. */ public async refreshServerPlanetStates(planetIds: LocationId[]) { const planets = this.getPlanetsWithIds(planetIds); planetIds.forEach((id) => this.getGameObjects().updatePlanet(id, (p) => { p.loadingServerState = true; }) ); const messages = await getMessagesOnPlanets({ planets: planetIds }); planets.forEach((planet) => { const previousPlanetEmoji = getEmojiMessage(planet); planet.messages = messages[planet.locationId]; const nowPlanetEmoji = getEmojiMessage(planet); // an emoji was added if (previousPlanetEmoji === undefined && nowPlanetEmoji !== undefined) { planet.emojiZoopAnimation = easeInAnimation(2000); // an emoji was removed } else if (nowPlanetEmoji === undefined && previousPlanetEmoji !== undefined) { planet.emojiZoopAnimation = undefined; planet.emojiZoopOutAnimation = emojiEaseOutAnimation(3000, previousPlanetEmoji.body.emoji); } }); planetIds.forEach((id) => this.getGameObjects().updatePlanet(id, (p) => { p.loadingServerState = false; p.needsServerRefresh = false; }) ); } /** * If you are the owner of this planet, you can set an 'emoji' to hover above the planet. * `emojiStr` must be a string that contains a single emoji, otherwise this function will throw an * error. * * The emoji is stored off-chain in a postgres database. We verify planet ownership via a contract * call from the webserver, and by verifying that the request to add (or remove) an emoji from a * planet was signed by the owner. */ public setPlanetEmoji(locationId: LocationId, emojiStr: string) { return this.submitPlanetMessage(locationId, PlanetMessageType.EmojiFlag, { emoji: emojiStr, }); } /** * If you are the owner of this planet, you can delete the emoji that is hovering above the * planet. */ public async clearEmoji(locationId: LocationId) { if (this.account === undefined) { throw new Error("can't clear emoji: not logged in"); } if (this.getPlanetWithId(locationId)?.unconfirmedClearEmoji) { throw new Error(`can't clear emoji: alreading clearing emoji from ${locationId}`); } this.getGameObjects().updatePlanet(locationId, (p) => { p.unconfirmedClearEmoji = true; }); const request = await this.ethConnection.signMessageObject({ locationId, ids: this.getPlanetWithId(locationId)?.messages?.map((m) => m.id) || [], }); try { await deleteMessages(request); } catch (e) { throw e; } finally { this.getGameObjects().updatePlanet(locationId, (p) => { p.needsServerRefresh = true; p.unconfirmedClearEmoji = false; }); } await this.refreshServerPlanetStates([locationId]); } public async submitDisconnectTwitter(twitter: string) { await disconnectTwitter(await this.ethConnection.signMessageObject({ twitter })); await this.refreshTwitters(); } /** * The planet emoji feature is built on top of a more general 'Planet Message' system, which * allows players to upload pieces of data called 'Message's to planets that they own. Emojis are * just one type of message. Their implementation leaves the door open to more off-chain data. */ private async submitPlanetMessage( locationId: LocationId, type: PlanetMessageType, body: unknown ) { if (this.account === undefined) { throw new Error("can't submit planet message not logged in"); } if (this.getPlanetWithId(locationId)?.unconfirmedAddEmoji) { throw new Error(`can't submit planet message: already submitting for planet ${locationId}`); } this.getGameObjects().updatePlanet(locationId, (p) => { p.unconfirmedAddEmoji = true; }); const request = await this.ethConnection.signMessageObject({ locationId, sender: this.account, type, body, }); try { await addMessage(request); } catch (e) { throw e; } finally { this.getGameObjects().updatePlanet(locationId, (p) => { p.unconfirmedAddEmoji = false; p.needsServerRefresh = true; }); } await this.refreshServerPlanetStates([locationId]); } /** * Checks that a message signed by {@link GameManager#signMessage} was signed by the address that * it claims it was signed by. */ private async verifyMessage(message: SignedMessage): Promise { const preSigned = JSON.stringify(message.message); return verifySignature(preSigned, message.signature as string, message.sender); } /** * Submits a transaction to the blockchain to move the given amount of resources from * the given planet to the given planet. */ public async move( from: LocationId, to: LocationId, forces: number, silver: number, artifactMoved?: ArtifactId, abandoning = false, bypassChecks = false ): Promise> { localStorage.setItem(`${this.getAccount()?.toLowerCase()}-fromPlanet`, from); localStorage.setItem(`${this.getAccount()?.toLowerCase()}-toPlanet`, to); try { if (!bypassChecks && this.checkGameHasEnded()) { throw new Error('game has ended'); } const arrivalsToOriginPlanet = this.entityStore.getArrivalIdsForLocation(from); const hasIncomingVoyage = arrivalsToOriginPlanet && arrivalsToOriginPlanet.length > 0; if (abandoning && hasIncomingVoyage) { throw new Error('cannot abandon a planet that has incoming voyages'); } const oldLocation = this.entityStore.getLocationOfPlanet(from); const newLocation = this.entityStore.getLocationOfPlanet(to); if (!oldLocation) { throw new Error('tried to move from planet that does not exist'); } if (!newLocation) { throw new Error('tried to move from planet that does not exist'); } const oldX = oldLocation.coords.x; const oldY = oldLocation.coords.y; const newX = newLocation.coords.x; const newY = newLocation.coords.y; const xDiff = newX - oldX; const yDiff = newY - oldY; const distMax = Math.ceil(Math.sqrt(xDiff ** 2 + yDiff ** 2)); // Contract will automatically send full forces/silver on abandon const shipsMoved = !abandoning ? forces : 0; const silverMoved = !abandoning ? silver : 0; if (newX ** 2 + newY ** 2 >= this.worldRadius ** 2) { throw new Error('attempted to move out of bounds'); } const oldPlanet = this.entityStore.getPlanetWithLocation(oldLocation); if ( ((!bypassChecks && !this.account) || !oldPlanet || oldPlanet.owner !== this.account) && !isSpaceShip(this.getArtifactWithId(artifactMoved)?.artifactType) ) { throw new Error('attempted to move from a planet not owned by player'); } const getArgs = async (): Promise => { const snarkArgs = await this.snarkHelper.getMoveArgs( oldX, oldY, newX, newY, this.worldRadius, distMax ); const args: MoveArgs = [ snarkArgs[ZKArgIdx.PROOF_A], snarkArgs[ZKArgIdx.PROOF_B], snarkArgs[ZKArgIdx.PROOF_C], [ ...snarkArgs[ZKArgIdx.DATA], (shipsMoved * CONTRACT_PRECISION).toString(), (silverMoved * CONTRACT_PRECISION).toString(), '0', abandoning ? '1' : '0', ], ] as MoveArgs; this.terminal.current?.println('MOVE: calculated SNARK with args:', TerminalTextStyle.Sub); this.terminal.current?.println( JSON.stringify(hexifyBigIntNestedArray(args)), TerminalTextStyle.Sub ); this.terminal.current?.newline(); if (artifactMoved) { args[ZKArgIdx.DATA][MoveArgIdxs.ARTIFACT_SENT] = artifactIdToDecStr(artifactMoved); } return args; }; const txIntent: UnconfirmedMove = { methodName: 'move', contract: this.contractsAPI.contract, args: getArgs(), from: oldLocation.hash, to: newLocation.hash, forces: shipsMoved, silver: silverMoved, artifact: artifactMoved, abandoning, }; if (artifactMoved) { const artifact = this.entityStore.getArtifactById(artifactMoved); if (!bypassChecks) { if (!artifact) { throw new Error("couldn't find this artifact"); } if (isActivated(artifact)) { throw new Error("can't move an activated artifact"); } if (!oldPlanet?.heldArtifactIds?.includes(artifactMoved)) { throw new Error("that artifact isn't on this planet!"); } } } // Always await the submitTransaction so we can catch rejections const tx = await this.contractsAPI.submitTransaction(txIntent); return tx; } catch (e) { this.getNotificationsManager().txInitError('move', e.message); throw e; } } /** * Submits a transaction to the blockchain to upgrade the given planet with the given * upgrade branch. You must own the planet, and have enough silver on it to complete * the upgrade. */ public async upgrade( planetId: LocationId, branch: number, _bypassChecks = false ): Promise> { try { // this is shitty localStorage.setItem(`${this.getAccount()?.toLowerCase()}-upPlanet`, planetId); localStorage.setItem(`${this.getAccount()?.toLowerCase()}-branch`, branch.toString()); const txIntent: UnconfirmedUpgrade = { methodName: 'upgradePlanet', contract: this.contractsAPI.contract, args: Promise.resolve([locationIdToDecStr(planetId), branch.toString()]), locationId: planetId, upgradeBranch: branch, }; // Always await the submitTransaction so we can catch rejections const tx = await this.contractsAPI.submitTransaction(txIntent); return tx; } catch (e) { this.getNotificationsManager().txInitError('upgradePlanet', e.message); throw e; } } /** * Submits a transaction to the blockchain to buy a hat for the given planet. You must own the * planet. Warning costs real xdai. Hats are permanently locked to a planet. They are purely * cosmetic and a great way to BM your opponents or just look your best. Just like in the real * world, more money means more hat. */ public async buyHat( planetId: LocationId, _bypassChecks = false ): Promise> { const planetLoc = this.entityStore.getLocationOfPlanet(planetId); const planet = this.entityStore.getPlanetWithLocation(planetLoc); try { if (!planetLoc) { console.error('planet not found'); throw new Error('[TX ERROR] Planet not found'); } if (!planet) { console.error('planet not found'); throw new Error('[TX ERROR] Planet not found'); } localStorage.setItem(`${this.getAccount()?.toLowerCase()}-hatPlanet`, planetId); localStorage.setItem( `${this.getAccount()?.toLowerCase()}-hatLevel`, planet.hatLevel.toString() ); const txIntent: UnconfirmedBuyHat = { methodName: 'buyHat', contract: this.contractsAPI.contract, args: Promise.resolve([locationIdToDecStr(planetId)]), locationId: planetId, }; // Always await the submitTransaction so we can catch rejections const tx = await this.contractsAPI.submitTransaction(txIntent, { gasLimit: 500000, value: bigInt(1000000000000000000) .multiply(2 ** planet.hatLevel) .toString(), }); return tx; } catch (e) { this.getNotificationsManager().txInitError('buyHat', e.message); throw e; } } // TODO: Change this to transferPlanet in a breaking release public async transferOwnership( planetId: LocationId, newOwner: EthAddress, bypassChecks = false ): Promise> { try { if (!bypassChecks) { if (this.checkGameHasEnded()) { throw new Error('game has ended'); } const planetLoc = this.entityStore.getLocationOfPlanet(planetId); if (!planetLoc) { console.error('planet not found'); throw new Error('[TX ERROR] Planet not found'); } const planet = this.entityStore.getPlanetWithLocation(planetLoc); if (!planet) { console.error('planet not found'); throw new Error('[TX ERROR] Planet not found'); } } localStorage.setItem(`${this.getAccount()?.toLowerCase()}-transferPlanet`, planetId); localStorage.setItem(`${this.getAccount()?.toLowerCase()}-transferOwner`, newOwner); const txIntent: UnconfirmedPlanetTransfer = { methodName: 'transferPlanet', contract: this.contractsAPI.contract, args: Promise.resolve([locationIdToDecStr(planetId), newOwner]), planetId, newOwner, }; // Always await the submitTransaction so we can catch rejections const tx = await this.contractsAPI.submitTransaction(txIntent); return tx; } catch (e) { this.getNotificationsManager().txInitError('transferPlanet', e.message); throw e; } } /** * Makes this game manager aware of a new chunk - which includes its location, size, * as well as all of the planets contained in that chunk. Causes the client to load * all of the information about those planets from the blockchain. */ addNewChunk(chunk: Chunk): GameManager { this.persistentChunkStore.addChunk(chunk, true); for (const planetLocation of chunk.planetLocations) { this.entityStore.addPlanetLocation(planetLocation); if (this.entityStore.isPlanetInContract(planetLocation.hash)) { this.hardRefreshPlanet(planetLocation.hash); // don't need to await, just start the process of hard refreshing } } return this; } listenForNewBlock() { this.getEthConnection().blockNumber$.subscribe((blockNumber) => { if (this.captureZoneGenerator) { this.captureZoneGenerator.generate(blockNumber); } }); } /** * To add multiple chunks at once, use this function rather than `addNewChunk`, in order * to load all of the associated planet data in an efficient manner. */ async bulkAddNewChunks(chunks: Chunk[]): Promise { this.terminal.current?.println( 'IMPORTING MAP: if you are importing a large map, this may take a while...' ); const planetIdsToUpdate: LocationId[] = []; for (const chunk of chunks) { this.persistentChunkStore.addChunk(chunk, true); for (const planetLocation of chunk.planetLocations) { this.entityStore.addPlanetLocation(planetLocation); if (this.entityStore.isPlanetInContract(planetLocation.hash)) { // Await this so we don't crash the game planetIdsToUpdate.push(planetLocation.hash); } } } this.terminal.current?.println( `downloading data for ${planetIdsToUpdate.length} planets...`, TerminalTextStyle.Sub ); this.bulkHardRefreshPlanets(planetIdsToUpdate); } // utils - scripting only /** * Gets the maximuim distance that you can send your energy from the given planet, * using the given percentage of that planet's current silver. */ getMaxMoveDist(planetId: LocationId, sendingPercent: number, abandoning: boolean): number { const planet = this.getPlanetWithId(planetId); if (!planet) throw new Error('origin planet unknown'); return getRange(planet, sendingPercent, this.getRangeBuff(abandoning)); } /** * Gets the distance between two planets. Throws an exception if you don't * know the location of either planet. Takes into account wormholes. */ getDist(fromId: LocationId, toId: LocationId): number { const from = this.entityStore.getPlanetWithId(fromId); const to = this.entityStore.getPlanetWithId(toId); if (!from) throw new Error('origin planet unknown'); if (!to) throw new Error('destination planet unknown'); if (!isLocatable(from)) throw new Error('origin location unknown'); if (!isLocatable(to)) throw new Error('destination location unknown'); const wormholeFactors = this.getWormholeFactors(from, to); let distance = this.getDistCoords(from.location.coords, to.location.coords); if (wormholeFactors) { distance /= wormholeFactors.distanceFactor; } return distance; } /** * Gets the distance between two coordinates in space. */ getDistCoords(fromCoords: WorldCoords, toCoords: WorldCoords) { return Math.sqrt((fromCoords.x - toCoords.x) ** 2 + (fromCoords.y - toCoords.y) ** 2); } /** * Gets all the planets that you can reach with at least 1 energy from * the given planet. Does not take into account wormholes. */ getPlanetsInRange(planetId: LocationId, sendingPercent: number, abandoning: boolean): Planet[] { const planet = this.entityStore.getPlanetWithId(planetId); if (!planet) throw new Error('planet unknown'); if (!isLocatable(planet)) throw new Error('planet location unknown'); // Performance improvements originally suggested by [@modokon](https://github.com/modukon) // at https://github.com/darkforest-eth/client/issues/15 // Improved by using `planetMap` by [@phated](https://github.com/phated) const result = []; const range = getRange(planet, sendingPercent, this.getRangeBuff(abandoning)); for (const p of this.getPlanetMap().values()) { if (isLocatable(p)) { if (this.getDistCoords(planet.location.coords, p.location.coords) < range) { result.push(p); } } } return result; } /** * Gets the amount of energy needed in order for a voyage from the given to the given * planet to arrive with your desired amount of energy. */ getEnergyNeededForMove( fromId: LocationId, toId: LocationId, arrivingEnergy: number, abandoning = false ): number { const from = this.getPlanetWithId(fromId); if (!from) throw new Error('origin planet unknown'); const dist = this.getDist(fromId, toId); const range = from.range * this.getRangeBuff(abandoning); const rangeSteps = dist / range; const arrivingProp = arrivingEnergy / from.energyCap + 0.05; return arrivingProp * Math.pow(2, rangeSteps) * from.energyCap; } /** * Gets the amount of energy that would arrive if a voyage with the given parameters * was to occur. The toPlanet is optional, in case you want an estimate that doesn't include * wormhole speedups. */ getEnergyArrivingForMove( fromId: LocationId, toId: LocationId | undefined, distance: number | undefined, sentEnergy: number, abandoning: boolean ) { const from = this.getPlanetWithId(fromId); const to = this.getPlanetWithId(toId); if (!from) throw new Error(`unknown planet`); if (distance === undefined && toId === undefined) throw new Error(`you must provide either a target planet or a distance`); const dist = (toId && this.getDist(fromId, toId)) || (distance as number); if (to && toId) { const wormholeFactors = this.getWormholeFactors(from, to); if (wormholeFactors !== undefined) { if (to.owner !== from.owner) { return 0; } } } const range = from.range * this.getRangeBuff(abandoning); const scale = (1 / 2) ** (dist / range); let ret = scale * sentEnergy - 0.05 * from.energyCap; if (ret < 0) ret = 0; return ret; } /** * Gets the active artifact on this planet, if one exists. */ getActiveArtifact(planet: Planet): Artifact | undefined { const artifacts = this.getArtifactsWithIds(planet.heldArtifactIds); const active = artifacts.find((a) => a && isActivated(a)); return active; } /** * If there's an active artifact on either of these planets which happens to be a wormhole which * is active and targetting the other planet, return the wormhole boost which is greater. Values * represent a multiplier. */ getWormholeFactors( fromPlanet: Planet, toPlanet: Planet ): { distanceFactor: number; speedFactor: number } | undefined { const fromActiveArtifact = this.getActiveArtifact(fromPlanet); const toActiveArtifact = this.getActiveArtifact(toPlanet); let greaterRarity: ArtifactRarity | undefined; if ( fromActiveArtifact?.artifactType === ArtifactType.Wormhole && fromActiveArtifact.wormholeTo === toPlanet.locationId ) { greaterRarity = fromActiveArtifact.rarity; } if ( toActiveArtifact?.artifactType === ArtifactType.Wormhole && toActiveArtifact.wormholeTo === fromPlanet.locationId ) { if (greaterRarity === undefined) { greaterRarity = toActiveArtifact.rarity; } else { greaterRarity = Math.max(greaterRarity, toActiveArtifact.rarity) as ArtifactRarity; } } const rangeUpgradesPerRarity = [0, 2, 4, 6, 8, 10]; const speedUpgradesPerRarity = [0, 10, 20, 30, 40, 50]; if (!greaterRarity || greaterRarity <= ArtifactRarity.Unknown) { return undefined; } return { distanceFactor: rangeUpgradesPerRarity[greaterRarity], speedFactor: speedUpgradesPerRarity[greaterRarity], }; } /** * Gets the amount of time, in seconds that a voyage between from the first to the * second planet would take. */ getTimeForMove(fromId: LocationId, toId: LocationId, abandoning = false): number { const from = this.getPlanetWithId(fromId); if (!from) throw new Error('origin planet unknown'); const dist = this.getDist(fromId, toId); const speed = from.speed * this.getSpeedBuff(abandoning); return dist / (speed / 100); } /** * Gets the temperature of a given location. */ getTemperature(coords: WorldCoords): number { const p = this.spaceTypePerlin(coords, false); return (16 - p) * 16; } /** * Load the serialized versions of all the plugins that this player has. */ public async loadPlugins(): Promise { return this.persistentChunkStore.loadPlugins(); } /** * Overwrites all the saved plugins to equal the given array of plugins. */ public async savePlugins(savedPlugins: SerializedPlugin[]): Promise { await this.persistentChunkStore.savePlugins(savedPlugins); } /** * Whether or not the given planet is capable of minting an artifact. */ public isPlanetMineable(p: Planet): boolean { return p.planetType === PlanetType.RUINS; } /** * Returns constructors of classes that may be useful for developing plugins. */ // eslint-disable-next-line @typescript-eslint/no-explicit-any public getConstructors() { return { MinerManager, SpiralPattern, SwissCheesePattern, TowardsCenterPattern, TowardsCenterPatternV2, }; } /** * Gets the perlin value at the given location in the world. SpaceType is based * on this value. */ public spaceTypePerlin(coords: WorldCoords, floor: boolean): number { return perlin(coords, { key: this.hashConfig.spaceTypeKey, scale: this.hashConfig.perlinLengthScale, mirrorX: this.hashConfig.perlinMirrorX, mirrorY: this.hashConfig.perlinMirrorY, floor, }); } /** * Gets the biome perlin valie at the given location in the world. */ public biomebasePerlin(coords: WorldCoords, floor: boolean): number { return perlin(coords, { key: this.hashConfig.biomebaseKey, scale: this.hashConfig.perlinLengthScale, mirrorX: this.hashConfig.perlinMirrorX, mirrorY: this.hashConfig.perlinMirrorY, floor, }); } public locationBigIntFromCoords(coords: WorldCoords): BigInteger { return this.planetHashMimc(coords.x, coords.y); } /** * Helpful for listening to user input events. */ public getUIEventEmitter() { return UIEmitter.getInstance(); } public getCaptureZoneGenerator() { return this.captureZoneGenerator; } /** * Emits when new capture zones are generated. */ public get captureZoneGeneratedEmitter(): Monomitter | undefined { return this.captureZoneGenerator?.generated$; } public getNotificationsManager() { return NotificationManager.getInstance(); } getWormholes(): Iterable { return this.entityStore.getWormholes(); } /** Return a reference to the planet map */ public getPlanetMap(): Map { return this.entityStore.getPlanetMap(); } /** Return a reference to the artifact map */ public getArtifactMap(): Map { return this.entityStore.getArtifactMap(); } /** Return a reference to the map of my planets */ public getMyPlanetMap(): Map { return this.entityStore.getMyPlanetMap(); } /** Return a reference to the map of my artifacts */ public getMyArtifactMap(): Map { return this.entityStore.getMyArtifactMap(); } public getPlanetUpdated$(): Monomitter { return this.entityStore.planetUpdated$; } public getArtifactUpdated$(): Monomitter { return this.entityStore.artifactUpdated$; } public getMyPlanetsUpdated$(): Monomitter> { return this.entityStore.myPlanetsUpdated$; } public getMyArtifactsUpdated$(): Monomitter> { return this.entityStore.myArtifactsUpdated$; } /** * Returns an instance of a `Contract` from the ethersjs library. This is the library we use to * connect to the blockchain. For documentation about how `Contract` works, see: * https://docs.ethers.io/v5/api/contract/contract/ * * Also, registers your contract in the system to make calls against it and to reload it when * necessary (such as the RPC endpoint changing). */ public loadContract( contractAddress: string, contractABI: ContractInterface ): Promise { return this.ethConnection.loadContract(contractAddress, async (address, provider, signer) => createContract(address, contractABI, provider, signer) ); } public testNotification() { NotificationManager.getInstance().reallyLongNotification(); } /** * Gets a reference to the game's internal representation of the world state. This includes * voyages, planets, artifacts, and active wormholes, */ public getGameObjects(): GameObjects { return this.entityStore; } public forceTick(locationId: LocationId) { this.getGameObjects().forceTick(locationId); } /** * Gets some diagnostic information about the game. Returns a copy, you can't modify it. */ public getDiagnostics(): Diagnostics { return { ...this.diagnostics }; } /** * Updates the diagnostic info of the game using the supplied function. Ideally, each spot in the * codebase that would like to record a metric is able to update its specific metric in a * convenient manner. */ public updateDiagnostics(updateFn: (d: Diagnostics) => void): void { updateFn(this.diagnostics); } /** * Listen for changes to a planet take action, * eg. * waitForPlanet("yourAsteroidId", ({current}) => current.silverCap / current.silver > 90) * .then(() => { * // Send Silver to nearby planet * }) * * @param locationId A locationId to watch for updates * @param predicate a function that accepts a Diff and should return a truth-y value, value will be passed to promise.resolve() * @returns a promise that will resolve with results returned from the predicate function */ public waitForPlanet( locationId: LocationId, predicate: ({ current, previous }: Diff) => T | undefined ): Promise { const disposableEmitter = getDisposableEmitter( this.getPlanetMap(), locationId, this.getPlanetUpdated$() ); const diffEmitter = generateDiffEmitter(disposableEmitter); return new Promise((resolve, reject) => { diffEmitter.subscribe(({ current, previous }: Diff) => { try { const predicateResults = predicate({ current, previous }); if (!!predicateResults) { disposableEmitter.clear(); diffEmitter.clear(); resolve(predicateResults); } } catch (err) { disposableEmitter.clear(); diffEmitter.clear(); reject(err); } }); }); } public getSafeMode() { return this.safeMode; } public setSafeMode(safeMode: boolean) { this.safeMode = safeMode; } public getAddress() { return this.ethConnection.getAddress(); } public isAdmin(): boolean { return this.getAddress() === this.contractConstants.adminAddress; } /** * Right now the only buffs supported in this way are * speed/range buffs from Abandoning a planet. * * The abandoning argument is used when interacting with * this function programmatically. */ public getSpeedBuff(abandoning: boolean): number { const { SPACE_JUNK_ENABLED, ABANDON_SPEED_CHANGE_PERCENT } = this.contractConstants; if (SPACE_JUNK_ENABLED && abandoning) { return ABANDON_SPEED_CHANGE_PERCENT / 100; } return 1; } public getRangeBuff(abandoning: boolean): number { const { SPACE_JUNK_ENABLED, ABANDON_RANGE_CHANGE_PERCENT } = this.contractConstants; if (SPACE_JUNK_ENABLED && abandoning) { return ABANDON_RANGE_CHANGE_PERCENT / 100; } return 1; } public getSnarkHelper(): SnarkArgsHelper { return this.snarkHelper; } public async submitTransaction( txIntent: T, overrides?: providers.TransactionRequest ): Promise> { return this.contractsAPI.submitTransaction(txIntent, overrides); } public getContract(): DarkForest { return this.contractsAPI.contract; } public getPaused(): boolean { return this.paused; } public getPaused$(): Monomitter { return this.paused$; } } export default GameManager; ================================================ FILE: src/Backend/GameLogic/GameObjects.ts ================================================ import { EMPTY_ADDRESS, MAX_PLANET_LEVEL, MIN_PLANET_LEVEL } from '@darkforest_eth/constants'; import { Monomitter, monomitter } from '@darkforest_eth/events'; import { hasOwner, isActivated, isLocatable } from '@darkforest_eth/gamelogic'; import { bonusFromHex, getBytesFromHex } from '@darkforest_eth/hexgen'; import { TxCollection } from '@darkforest_eth/network'; import { isUnconfirmedActivateArtifact, isUnconfirmedActivateArtifactTx, isUnconfirmedBuyHat, isUnconfirmedBuyHatTx, isUnconfirmedCapturePlanetTx, isUnconfirmedDeactivateArtifact, isUnconfirmedDeactivateArtifactTx, isUnconfirmedDepositArtifact, isUnconfirmedDepositArtifactTx, isUnconfirmedFindArtifact, isUnconfirmedFindArtifactTx, isUnconfirmedGetShipsTx, isUnconfirmedInvadePlanetTx, isUnconfirmedMove, isUnconfirmedMoveTx, isUnconfirmedProspectPlanet, isUnconfirmedProspectPlanetTx, isUnconfirmedReveal, isUnconfirmedRevealTx, isUnconfirmedTransfer, isUnconfirmedTransferTx, isUnconfirmedUpgrade, isUnconfirmedUpgradeTx, isUnconfirmedWithdrawArtifact, isUnconfirmedWithdrawArtifactTx, isUnconfirmedWithdrawSilver, isUnconfirmedWithdrawSilverTx, } from '@darkforest_eth/serde'; import { Abstract, ArrivalWithTimer, Artifact, ArtifactId, ArtifactType, Biome, Chunk, ClaimedLocation, EthAddress, LocatablePlanet, LocationId, Planet, PlanetLevel, PlanetType, QueuedArrival, Radii, RevealedLocation, SpaceType, Transaction, TransactionCollection, VoyageId, WorldCoords, WorldLocation, Wormhole, } from '@darkforest_eth/types'; import autoBind from 'auto-bind'; import bigInt from 'big-integer'; import { ethers } from 'ethers'; import _ from 'lodash'; import NotificationManager from '../../Frontend/Game/NotificationManager'; import { getArtifactId, getArtifactOwner, getPlanetId, getPlanetOwner, setObjectSyncState, } from '../../Frontend/Utils/EmitterUtils'; import { ContractConstants } from '../../_types/darkforest/api/ContractsAPITypes'; import { arrive, PlanetDiff, updatePlanetToTime } from './ArrivalUtils'; import { LayeredMap } from './LayeredMap'; type CoordsString = Abstract; const getCoordsString = (coords: WorldCoords): CoordsString => { return `${coords.x},${coords.y}` as CoordsString; }; /** * Representation of the objects which exist in the world. */ export class GameObjects { /** * This is a data structure that allows us to efficiently calculate which planets are visible on * the player's screen given the viewport's position and size. */ private readonly layeredMap: LayeredMap; /** * This address of the player that is currently logged in. * * @todo move this, along with all other objects relating to the currently logged-on player into a * new field: {@code player: PlayerInfo} */ private readonly address: EthAddress | undefined; /** * Cached index of all known planet data. * * Warning! * * This should NEVER be set to directly! Any time you want to update a planet, you must call the * {@link GameObjects#setPlanet()} function. Following this rule enables us to reliably notify * other parts of the client when a particular object has been updated. TODO: what is the best way * to do this? * * @todo extract the pattern we're using for the field tuples * - {planets, myPlanets, myPlanetsUpdated, planetUpdated$} * - {artifacts, myArtifacts, myArtifactsUpdated, artifactUpdated$} * * into some sort of class. */ private readonly planets: Map; /** * Cached index of planets owned by the player. * * @see The same warning applys as the one on {@link GameObjects.planets} */ private readonly myPlanets: Map; /** * Cached index of all known artifact data. * * @see The same warning applys as the one on {@link GameObjects.planets} */ private readonly artifacts: Map; /** * Cached index of artifacts owned by the player. * * @see The same warning applys as the one on {@link GameObjects.planets} */ private readonly myArtifacts: Map; /** * Map from artifact ids to wormholes. */ private readonly wormholes: Map; /** * Set of all planet ids that we know have been interacted-with on-chain. */ private readonly touchedPlanetIds: Set; /** * Map of arrivals to timers that fire when an arrival arrives, in case that handler needs to be * cancelled for whatever reason. */ private readonly arrivals: Map; /** * Map from a location id (think of it as the unique id of each planet) to all the ids of the * voyages that are arriving on that planet. These include both the player's own voyages, and also * any potential invader's voyages. */ private readonly planetArrivalIds: Map; /** * Map from location id (unique id of each planet) to some information about the location at which * this planet is located, if this client happens to know the coordinates of this planet. */ private readonly planetLocationMap: Map; /** * Map from location ids to, if that location id has been revealed on-chain, the world coordinates * of that location id, as well as some extra information regarding the circumstances of the * revealing of this planet. */ private readonly revealedLocations: Map; /** * Map from location ids to, if that location id has been claimed on-chain, the world coordinates * of that location id, as well as some extra information regarding the circumstances of the * revealing of this planet. */ private readonly claimedLocations: Map; /** * Some of the game's parameters are downloaded from the blockchain. This allows the client to be * flexible, and connect to any compatible set of Dark Forest contracts, download the parameters, * and join the game, taking into account the unique configuration of those specific Dark Forest * contracts. */ private readonly contractConstants: ContractConstants; /** * Map from a stringified representation of an x-y coordinate to an object that contains some more * information about the world at that location. */ private readonly coordsToLocation: Map; /** * Transactions that are currently in flight. */ public readonly transactions: TransactionCollection; /** * Event emitter which publishes whenever a planet is updated. */ public readonly planetUpdated$: Monomitter; /** * Event emitter which publishes whenever an artifact has been updated. */ public readonly artifactUpdated$: Monomitter; /** * Whenever a planet is updated, we publish to this event with a reference to a map from location * id to planet. We need to rethink this event emitter because it currently publishes every time * that any planet is updated, and if a lot of them are updated at once (which i think is the case * once every two minutes) then this event emitter will publish a shitton of events. * TODO: rethink this */ public readonly myPlanetsUpdated$: Monomitter>; /** * Whenever one of the player's artifacts are updated, this event emitter publishes. See * {@link GameObjects.myPlanetsUpdated$} for more info. */ public readonly myArtifactsUpdated$: Monomitter>; constructor( address: EthAddress | undefined, touchedPlanets: Map, allTouchedPlanetIds: Set, revealedLocations: Map, claimedLocations: Map, artifacts: Map, allChunks: Iterable, unprocessedArrivals: Map, unprocessedPlanetArrivalIds: Map, contractConstants: ContractConstants, worldRadius: number ) { autoBind(this); this.address = address; this.planets = touchedPlanets; this.myPlanets = new Map(); this.touchedPlanetIds = allTouchedPlanetIds; this.revealedLocations = revealedLocations; this.claimedLocations = claimedLocations; this.artifacts = artifacts; this.myArtifacts = new Map(); this.contractConstants = contractConstants; this.coordsToLocation = new Map(); this.planetLocationMap = new Map(); const planetArrivalIds = new Map(); const arrivals = new Map(); this.transactions = new TxCollection(); this.wormholes = new Map(); this.layeredMap = new LayeredMap(worldRadius); this.planetUpdated$ = monomitter(); this.artifactUpdated$ = monomitter(); this.myArtifactsUpdated$ = monomitter(); this.myPlanetsUpdated$ = monomitter(); for (const chunk of allChunks) { for (const planetLocation of chunk.planetLocations) { this.addPlanetLocation(planetLocation); } } for (const location of revealedLocations.values()) { this.markLocationRevealed(location); this.addPlanetLocation(location); } this.replaceArtifactsFromContractData(artifacts.values()); touchedPlanets.forEach((planet, planetId) => { const arrivalIds = unprocessedPlanetArrivalIds.get(planetId); if (planet && arrivalIds) { const arrivalsForPlanetNull: (QueuedArrival | undefined)[] = arrivalIds.map((arrivalId) => unprocessedArrivals.get(arrivalId) ); const arrivalsForPlanet: QueuedArrival[] = arrivalsForPlanetNull.filter( (x) => !!x ) as QueuedArrival[]; const revealedLocation = revealedLocations.get(planetId); if (revealedLocation) { planet.coordsRevealed = true; planet.revealer = revealedLocation.revealer; } const arrivalsWithTimers = this.processArrivalsForPlanet( planet.locationId, arrivalsForPlanet ); planetArrivalIds.set( planetId, arrivalsWithTimers.map((arrival) => arrival.arrivalData.eventId) ); for (const arrivalWithTimer of arrivalsWithTimers) { const arrivalId = arrivalWithTimer.arrivalData.eventId; arrivals.set(arrivalId, arrivalWithTimer); } const planetLocation = this.planetLocationMap.get(planetId); if (planet && planetLocation) { (planet as LocatablePlanet).location = planetLocation; (planet as LocatablePlanet).biome = this.getBiome(planetLocation); } this.setPlanet(planet); } }); this.arrivals = arrivals; this.planetArrivalIds = planetArrivalIds; for (const [_locId, claimedLoc] of claimedLocations) { this.updatePlanet(claimedLoc.hash, (p) => { p.claimer = claimedLoc.revealer; }); } // TODO: do this better... // set interval to update all planets every 120s setInterval(() => { this.planets.forEach((planet) => { if (planet && hasOwner(planet)) { updatePlanetToTime( planet, this.getPlanetArtifacts(planet.locationId), Date.now(), this.contractConstants ); } }); }, 120 * 1000); } public getWormholes(): Iterable { return this.wormholes.values(); } public getArtifactById(artifactId?: ArtifactId): Artifact | undefined { return artifactId ? this.artifacts.get(artifactId) : undefined; } public getArtifactsOwnedBy(addr: EthAddress): Artifact[] { const ret: Artifact[] = []; this.artifacts.forEach((artifact) => { if (artifact.currentOwner === addr || artifact.controller === addr) { ret.push(artifact); } }); return ret; } public getPlanetArtifacts(planetId: LocationId): Artifact[] { return (this.planets.get(planetId)?.heldArtifactIds || []) .map((id) => this.artifacts.get(id)) .filter((a) => !!a) as Artifact[]; } public getArtifactsOnPlanetsOwnedBy(addr: EthAddress): Artifact[] { const ret: Artifact[] = []; this.artifacts.forEach((artifact) => { if (artifact.onPlanetId) { const planet = this.getPlanetWithId(artifact.onPlanetId, false); if (planet && planet.owner === addr) { ret.push(artifact); } } }); return ret; } // get planet by ID - must be in contract or known chunks public getPlanetWithId(planetId: LocationId, updateIfStale = true): Planet | undefined { const planet = this.planets.get(planetId); if (planet) { if (updateIfStale) { this.updatePlanetIfStale(planet); } return planet; } const loc = this.getLocationOfPlanet(planetId); if (!loc) return undefined; return this.getPlanetWithLocation(loc); } // returns undefined if this planet is neither in contract nor in known chunks // fast query that doesn't update planet if stale public getPlanetLevel(planetId: LocationId): PlanetLevel | undefined { const planet = this.planets.get(planetId); if (planet) { return planet.planetLevel; } return undefined; } // returns undefined if this planet is neither in contract nor in known chunks // fast query that doesn't update planet if stale public getPlanetDetailLevel(planetId: LocationId): number | undefined { const planet = this.planets.get(planetId); if (planet) { let detailLevel = planet.planetLevel as number; if (hasOwner(planet)) { detailLevel += 1; } return detailLevel; } else { return undefined; } } /** * received some artifact data from the contract. update our stores */ public replaceArtifactFromContractData(artifact: Artifact): void { const localArtifact = this.artifacts.get(artifact.id); if (localArtifact) { artifact.transactions = localArtifact.transactions; artifact.onPlanetId = localArtifact.onPlanetId; } this.setArtifact(artifact); } public replaceArtifactsFromContractData(artifacts: Iterable) { for (const artifact of artifacts) { this.replaceArtifactFromContractData(artifact); } } /** * Given a planet id, update the state of the given planet by calling the given update function. * If the planet was updated, then also publish the appropriate event. */ public updatePlanet(id: LocationId, updateFn: (p: Planet) => void) { const planet = this.getPlanetWithId(id); if (planet !== undefined) { updateFn(planet); this.setPlanet(planet); } } /** * Given a planet id, update the state of the given planet by calling the given update function. * If the planet was updated, then also publish the appropriate event. */ public updateArtifact(id: ArtifactId | undefined, updateFn: (p: Artifact) => void) { const artifact = this.getArtifactById(id); if (artifact !== undefined) { updateFn(artifact); this.setArtifact(artifact); } } /** * received some planet data from the contract. update our stores */ public replacePlanetFromContractData( planet: Planet, updatedArrivals?: QueuedArrival[], updatedArtifactsOnPlanet?: ArtifactId[], revealedLocation?: RevealedLocation, claimerEthAddress?: EthAddress // TODO: Remove this ): void { this.touchedPlanetIds.add(planet.locationId); // does not modify unconfirmed txs // that is handled by onTxConfirm const localPlanet = this.planets.get(planet.locationId); if (localPlanet) { const { transactions, loadingServerState, needsServerRefresh, lastLoadedServerState, emojiBobAnimation, emojiZoopAnimation, emojiZoopOutAnimation, messages, } = localPlanet; planet.transactions = transactions; planet.loadingServerState = loadingServerState; planet.needsServerRefresh = needsServerRefresh; planet.lastLoadedServerState = lastLoadedServerState; planet.emojiBobAnimation = emojiBobAnimation; planet.emojiZoopAnimation = emojiZoopAnimation; planet.emojiZoopOutAnimation = emojiZoopOutAnimation; planet.messages = messages; // Possibly non updated props planet.heldArtifactIds = localPlanet.heldArtifactIds; } else { this.planets.set(planet.locationId, planet); } if (updatedArtifactsOnPlanet) { planet.heldArtifactIds = updatedArtifactsOnPlanet; } // make planet Locatable if we know its location const loc = this.planetLocationMap.get(planet.locationId) || revealedLocation; if (loc) { (planet as LocatablePlanet).location = loc; (planet as LocatablePlanet).biome = this.getBiome(loc); } if (revealedLocation) { this.markLocationRevealed(revealedLocation); this.addPlanetLocation(revealedLocation); planet.coordsRevealed = true; planet.revealer = revealedLocation.revealer; } if (claimerEthAddress) { planet.claimer = claimerEthAddress; } this.setPlanet(planet); if (updatedArrivals) { // apply arrivals this.clearOldArrivals(planet); const updatedAwts = this.processArrivalsForPlanet(planet.locationId, updatedArrivals); for (const awt of updatedAwts) { const arrivalId = awt.arrivalData.eventId; this.arrivals.set(arrivalId, awt); const arrivalIds = this.planetArrivalIds.get(planet.locationId); if (arrivalIds) { arrivalIds.push(arrivalId); this.planetArrivalIds.set(planet.locationId, arrivalIds); } } } } // returns an empty planet if planet is not in contract // returns undefined if this isn't a planet, according to hash and coords public getPlanetWithCoords(coords: WorldCoords): LocatablePlanet | undefined { const str = getCoordsString(coords); const location = this.coordsToLocation.get(str); if (!location) { return undefined; } return this.getPlanetWithLocation(location) as LocatablePlanet; } // - returns an empty planet if planet is not in contract // - returns undefined if this isn't a planet, according to hash and coords // - if this planet hasn't been initialized in the client yet, initializes it public getPlanetWithLocation(location: WorldLocation | undefined): Planet | undefined { if (!location) return undefined; const planet = this.planets.get(location.hash); if (planet) { this.updatePlanetIfStale(planet); return planet; } // return a default unowned planet const defaultPlanet = this.defaultPlanetFromLocation(location); this.setPlanet(defaultPlanet); return defaultPlanet; } public isPlanetInContract(planetId: LocationId): boolean { return this.touchedPlanetIds.has(planetId); } /** * Called when we load chunk data into memory (on startup), when we're loading all revealed locations (on startup), * when miner has mined a new chunk while exploring, and when a planet's location is revealed onchain during the course of play * Adds a WorldLocation to the planetLocationMap, making it known to the player locally * Sets an unsynced default planet in the PlanetMap this.planets * IMPORTANT: This is the only way a LocatablePlanet gets constructed * IMPORTANT: Idempotent */ public addPlanetLocation(planetLocation: WorldLocation): void { this.layeredMap.insertPlanet( planetLocation, this.getPlanetWithId(planetLocation.hash, false)?.planetLevel ?? this.planetLevelFromHexPerlin(planetLocation.hash, planetLocation.perlin) ); this.planetLocationMap.set(planetLocation.hash, planetLocation); const str = getCoordsString(planetLocation.coords); if (!this.coordsToLocation.has(str)) { this.coordsToLocation.set(str, planetLocation); } if (!this.planets.get(planetLocation.hash)) { this.setPlanet(this.defaultPlanetFromLocation(planetLocation)); } const planet = this.planets.get(planetLocation.hash); if (planet) { (planet as LocatablePlanet).location = planetLocation; (planet as LocatablePlanet).biome = this.getBiome(planetLocation); } } // marks that a location is revealed on-chain public markLocationRevealed(revealedLocation: RevealedLocation): void { this.revealedLocations.set(revealedLocation.hash, revealedLocation); } public getLocationOfPlanet(planetId: LocationId): WorldLocation | undefined { return this.planetLocationMap.get(planetId) || undefined; } /** * Returns all planets in the game. * * Warning! Simply iterating over this is not performant, and is meant for scripting. * * @tutorial For plugin developers! */ public getAllPlanets(): Iterable { return this.planets.values(); } /** * Returns all planets in the game, as a map from their location id to the planet. * * @tutorial For plugin developers! * @see Warning in {@link GameObjects.getAllPlanets()} */ public getAllPlanetsMap(): Map { return this.planets; } /** * Returns all the planets in the game which this client is aware of that have an owner, as a map * from their id to the planet * * @tutorial For plugin developers! * @see Warning in {@link GameObjects.getAllPlanets()} */ public getAllOwnedPlanets(): Planet[] { return Array.from(this.planets.values()).filter(hasOwner); } /** * Returns all voyages that are scheduled to arrive at some point in the future. * * @tutorial For plugin developers! * @see Warning in {@link GameObjects.getAllPlanets()} */ public getAllVoyages(): QueuedArrival[] { return Array.from(this.arrivals.values()).map((awt) => awt.arrivalData); } /** * We call this function whenever the user requests that we send a transaction to the blockchain * with their localstorage wallet. You can think of it as one of the hubs which connects * `GameObjects` to the rest of the world. * * Inside this function, we update the relevant internal game objects to reflect that the user has * requested a particular action. Additionally, we publish the appropriate events to the relevant * {@link Monomitter} instances that are stored in this class. * * In the case of something like prospecting for an artifact, this allows us to display a spinner * text which says "Prospecting..." * * In the case of the user sending energy from one planet to another planet, this allows us to * display a dashed line between the two planets in their new voyage. * * Whenever we update an entity, we must do it via that entity's type's corresponding * `set` function, in order for us to publish these events. * * @todo: this entire function could be automated by implementing a new interface called * {@code TxFilter}. */ public onTxIntent(tx: Transaction) { this.transactions.addTransaction(tx); if (isUnconfirmedRevealTx(tx)) { const planet = this.getPlanetWithId(tx.intent.locationId); if (planet) { planet.transactions?.addTransaction(tx); this.setPlanet(planet); } } else if (isUnconfirmedMoveTx(tx)) { const planet = this.getPlanetWithId(tx.intent.from); if (planet) { planet.transactions?.addTransaction(tx); this.setPlanet(planet); } if (tx.intent.artifact) { const artifact = this.getArtifactById(tx.intent.artifact); if (artifact) { artifact.transactions?.addTransaction(tx); this.setArtifact(artifact); } } } else if (isUnconfirmedUpgradeTx(tx)) { const planet = this.getPlanetWithId(tx.intent.locationId); if (planet) { planet.transactions?.addTransaction(tx); this.setPlanet(planet); } } else if (isUnconfirmedBuyHatTx(tx)) { const planet = this.getPlanetWithId(tx.intent.locationId); if (planet) { planet.transactions?.addTransaction(tx); this.setPlanet(planet); } } else if (isUnconfirmedTransferTx(tx)) { const planet = this.getPlanetWithId(tx.intent.planetId); if (planet) { planet.transactions?.addTransaction(tx); this.setPlanet(planet); } } else if (isUnconfirmedProspectPlanetTx(tx)) { const planet = this.getPlanetWithId(tx.intent.planetId); if (planet) { planet.transactions?.addTransaction(tx); this.setPlanet(planet); } } else if (isUnconfirmedFindArtifactTx(tx)) { const planet = this.getPlanetWithId(tx.intent.planetId); if (planet) { planet.transactions?.addTransaction(tx); this.setPlanet(planet); } } else if (isUnconfirmedDepositArtifactTx(tx)) { const planet = this.getPlanetWithId(tx.intent.locationId); const artifact = this.getArtifactById(tx.intent.artifactId); if (planet) { planet.transactions?.addTransaction(tx); this.setPlanet(planet); } if (artifact) { artifact.transactions?.addTransaction(tx); this.setArtifact(artifact); } } else if (isUnconfirmedWithdrawArtifactTx(tx)) { const planet = this.getPlanetWithId(tx.intent.locationId); const artifact = this.getArtifactById(tx.intent.artifactId); if (planet) { planet.transactions?.addTransaction(tx); this.setPlanet(planet); } if (artifact) { artifact.transactions?.addTransaction(tx); this.setArtifact(artifact); } } else if (isUnconfirmedActivateArtifactTx(tx)) { const planet = this.getPlanetWithId(tx.intent.locationId); const artifact = this.getArtifactById(tx.intent.artifactId); if (planet) { planet.transactions?.addTransaction(tx); this.setPlanet(planet); } if (artifact) { artifact.transactions?.addTransaction(tx); this.setArtifact(artifact); } } else if (isUnconfirmedDeactivateArtifactTx(tx)) { const planet = this.getPlanetWithId(tx.intent.locationId); const artifact = this.getArtifactById(tx.intent.artifactId); if (planet) { planet.transactions?.addTransaction(tx); this.setPlanet(planet); } if (artifact) { artifact.transactions?.addTransaction(tx); this.setArtifact(artifact); } } else if (isUnconfirmedWithdrawSilverTx(tx)) { const planet = this.getPlanetWithId(tx.intent.locationId); if (planet) { planet.transactions?.addTransaction(tx); this.setPlanet(planet); } } else if (isUnconfirmedCapturePlanetTx(tx)) { const planet = this.getPlanetWithId(tx.intent.locationId); if (planet) { planet.transactions?.addTransaction(tx); this.setPlanet(planet); } } else if (isUnconfirmedInvadePlanetTx(tx)) { const planet = this.getPlanetWithId(tx.intent.locationId); if (planet) { planet.transactions?.addTransaction(tx); this.setPlanet(planet); } } } /** * Whenever a transaction that the user initiated either succeeds or fails, we need to clear the * fact that it was in progress from the event's corresponding entities. For example, whenever a * transaction that sends a voyage from one planet to another either succeeds or fails, we need to * remove the dashed line that connected them. * * Making sure that we never miss something here is very tedious. * * @todo Make this less tedious. */ public clearUnconfirmedTxIntent(tx: Transaction) { this.transactions.removeTransaction(tx); if (isUnconfirmedReveal(tx.intent)) { const planet = this.getPlanetWithId(tx.intent.locationId); if (planet) { planet.transactions?.removeTransaction(tx); this.setPlanet(planet); } } else if (isUnconfirmedMove(tx.intent)) { const planet = this.getPlanetWithId(tx.intent.from); if (planet) { planet.transactions?.removeTransaction(tx); this.setPlanet(planet); } if (tx.intent.artifact) { const artifact = this.getArtifactById(tx.intent.artifact); if (artifact) { artifact.transactions?.removeTransaction(tx); this.setArtifact(artifact); } } } else if (isUnconfirmedUpgrade(tx.intent)) { const planet = this.getPlanetWithId(tx.intent.locationId); if (planet) { planet.transactions?.removeTransaction(tx); this.setPlanet(planet); } } else if (isUnconfirmedBuyHat(tx.intent)) { const planet = this.getPlanetWithId(tx.intent.locationId); if (planet) { planet.transactions?.removeTransaction(tx); this.setPlanet(planet); } } else if (isUnconfirmedFindArtifact(tx.intent)) { const planet = this.getPlanetWithId(tx.intent.planetId); if (planet) { planet.transactions?.removeTransaction(tx); this.setPlanet(planet); } } else if (isUnconfirmedDepositArtifact(tx.intent)) { const planet = this.getPlanetWithId(tx.intent.locationId); const artifact = this.getArtifactById(tx.intent.artifactId); if (planet) { planet.transactions?.removeTransaction(tx); this.setPlanet(planet); } if (artifact) { artifact.transactions?.removeTransaction(tx); this.setArtifact(artifact); } } else if (isUnconfirmedWithdrawArtifact(tx.intent)) { const planet = this.getPlanetWithId(tx.intent.locationId); const artifact = this.getArtifactById(tx.intent.artifactId); if (planet) { planet.transactions?.removeTransaction(tx); this.setPlanet(planet); } if (artifact) { artifact.transactions?.removeTransaction(tx); this.setArtifact(artifact); } } else if (isUnconfirmedTransfer(tx.intent)) { const planet = this.getPlanetWithId(tx.intent.planetId); if (planet) { planet.transactions?.removeTransaction(tx); this.setPlanet(planet); } } else if (isUnconfirmedProspectPlanet(tx.intent)) { const planet = this.getPlanetWithId(tx.intent.planetId); if (planet) { planet.transactions?.removeTransaction(tx); this.setPlanet(planet); } } else if (isUnconfirmedActivateArtifact(tx.intent)) { const planet = this.getPlanetWithId(tx.intent.locationId); const artifact = this.getArtifactById(tx.intent.artifactId); if (planet) { planet.transactions?.removeTransaction(tx); this.setPlanet(planet); } if (artifact) { artifact.transactions?.removeTransaction(tx); this.setArtifact(artifact); } } else if (isUnconfirmedDeactivateArtifact(tx.intent)) { const planet = this.getPlanetWithId(tx.intent.locationId); const artifact = this.getArtifactById(tx.intent.artifactId); if (planet) { planet.transactions?.removeTransaction(tx); this.setPlanet(planet); } if (artifact) { artifact.transactions?.removeTransaction(tx); this.setArtifact(artifact); } } else if (isUnconfirmedWithdrawSilver(tx.intent)) { const planet = this.getPlanetWithId(tx.intent.locationId); if (planet) { planet.transactions?.removeTransaction(tx); this.setPlanet(planet); } } else if (isUnconfirmedCapturePlanetTx(tx)) { const planet = this.getPlanetWithId(tx.intent.locationId); if (planet) { planet.transactions?.removeTransaction(tx); this.setPlanet(planet); } } else if (isUnconfirmedInvadePlanetTx(tx)) { const planet = this.getPlanetWithId(tx.intent.locationId); if (planet) { planet.transactions?.removeTransaction(tx); this.setPlanet(planet); } } } public getPlanetMap(): Map { return this.planets; } public getArtifactMap(): Map { return this.artifacts; } public getMyPlanetMap(): Map { return this.myPlanets; } public getMyArtifactMap(): Map { return this.myArtifacts; } public getRevealedLocations(): Map { return this.revealedLocations; } public getClaimedLocations(): Map { return this.claimedLocations; } public setClaimedLocation(claimedLocation: ClaimedLocation) { this.claimedLocations.set(claimedLocation.hash, claimedLocation); } /** * Gets all the planets with the given ids, giltering out the ones that we don't have. */ public getPlanetsWithIds(locationIds: LocationId[], updateIfStale = true): Planet[] { return locationIds .map((id) => this.getPlanetWithId(id, updateIfStale)) .filter((p) => p !== undefined) as Planet[]; } /** * Gets all the planets that are within {@code radius} world units from the given coordinate. Fast * because it uses {@link LayeredMap}. */ public getPlanetsInWorldCircle(coords: WorldCoords, radius: number): LocatablePlanet[] { const locationIds = this.layeredMap.getPlanetsInCircle(coords, radius); return this.getPlanetsWithIds(locationIds) as LocatablePlanet[]; } /** * Gets the ids of all the planets that are both within the given bounding box (defined by its * bottom left coordinate, width, and height) in the world and of a level that was passed in via * the `planetLevels` parameter. Fast because it uses {@link LayeredMap}. */ public getPlanetsInWorldRectangle( worldX: number, worldY: number, worldWidth: number, worldHeight: number, levels: number[], planetLevelToRadii: Map, updateIfStale = true ): LocatablePlanet[] { const locationIds = this.layeredMap.getPlanets( worldX, worldY, worldWidth, worldHeight, levels, planetLevelToRadii ); return this.getPlanetsWithIds(locationIds, updateIfStale) as LocatablePlanet[]; } public forceTick(locationId: LocationId) { const planet = this.getPlanetWithId(locationId); if (planet) { this.setPlanet(planet); } } /** * Set a planet into our cached store. Should ALWAYS call this when setting a planet. * `this.planets` and `this.myPlanets` should NEVER be accessed directly! * This function also handles managing planet update messages and indexing the map of owned planets. * @param planet the planet to set */ private setPlanet(planet: Planet) { if (isLocatable(planet)) { this.layeredMap.insertPlanet(planet.location, planet.planetLevel); } setObjectSyncState( this.planets, this.myPlanets, this.address, this.planetUpdated$, this.myPlanetsUpdated$, getPlanetId, getPlanetOwner, planet ); } /** * Set an artifact into our cached store. Should ALWAYS call this when setting an artifact. * `this.artifacts` and `this.myArtifacts` should NEVER be accessed directly! * This function also handles managing artifact update messages and indexing the map of owned artifacts. * @param artifact the artifact to set */ private setArtifact(artifact: Artifact) { if (artifact.artifactType === ArtifactType.Wormhole && artifact.onPlanetId) { if (artifact.wormholeTo && isActivated(artifact)) { this.wormholes.set(artifact.id, { from: artifact.onPlanetId, to: artifact.wormholeTo, }); } else { this.wormholes.delete(artifact.id); } } setObjectSyncState( this.artifacts, this.myArtifacts, this.address, this.artifactUpdated$, this.myArtifactsUpdated$, getArtifactId, getArtifactOwner, artifact ); } /** * Emit notifications based on a planet's state change */ private emitArrivalNotifications({ previous, current, arrival }: PlanetDiff) { const notifManager = NotificationManager.getInstance(); if ( !GameObjects.planetCanUpgrade(previous) && GameObjects.planetCanUpgrade(current) && current.owner === this.address ) { notifManager.planetCanUpgrade(current); } if ( previous.owner !== this.address && previous.owner !== ethers.constants.AddressZero && current.owner === this.address ) { notifManager.planetConquered(current as LocatablePlanet); } if (previous.owner === this.address && current.owner !== this.address) { notifManager.planetLost(current as LocatablePlanet); } if ( arrival.player !== this.address && current.owner === this.address && arrival.energyArriving !== 0 ) { notifManager.planetAttacked(current as LocatablePlanet); } } private removeArrival(planetId: LocationId, arrivalId: VoyageId) { this.arrivals?.delete(arrivalId); const planetArrivalIds = this.planetArrivalIds?.get(planetId) ?? []; _.remove(planetArrivalIds, (id) => id === arrivalId); } private processArrivalsForPlanet( planetId: LocationId, arrivals: QueuedArrival[] ): ArrivalWithTimer[] { const planet = this.planets.get(planetId); if (!planet) { console.error(`attempted to process arrivals for planet not in memory: ${planetId}`); return []; } // process the QueuedArrival[] for a single planet const arrivalsWithTimers: ArrivalWithTimer[] = []; // sort arrivals by timestamp arrivals.sort((a, b) => a.arrivalTime - b.arrivalTime); const nowInSeconds = Date.now() / 1000; for (const arrival of arrivals) { try { if (nowInSeconds - arrival.arrivalTime > 0) { // if arrival happened in the past, run this arrival const update = arrive( planet, this.getPlanetArtifacts(planet.locationId), arrival, this.getArtifactById(arrival.artifactId), this.contractConstants ); this.removeArrival(planetId, update.arrival.eventId); this.emitArrivalNotifications(update); } else { // otherwise, set a timer to do this arrival in the future // and append it to arrivalsWithTimers const applyFutureArrival = setTimeout(() => { const update = arrive( planet, this.getPlanetArtifacts(planet.locationId), arrival, this.getArtifactById(arrival.artifactId), this.contractConstants ); this.emitArrivalNotifications(update); this.removeArrival(planetId, update.arrival.eventId); }, arrival.arrivalTime * 1000 - Date.now()); const arrivalWithTimer = { arrivalData: arrival, timer: applyFutureArrival, }; arrivalsWithTimers.push(arrivalWithTimer); } } catch (e) { console.error(`error occurred processing arrival for updated planet ${planetId}: ${e}`); } } return arrivalsWithTimers; } private clearOldArrivals(planet: Planet): void { const planetId = planet.locationId; // clear old timeouts const arrivalIds = this.planetArrivalIds.get(planetId); if (arrivalIds) { // clear if the planet already had stored arrivals for (const arrivalId of arrivalIds) { const arrivalWithTimer = this.arrivals.get(arrivalId); if (arrivalWithTimer) { clearTimeout(arrivalWithTimer.timer); } else { console.error(`arrival with id ${arrivalId} wasn't found`); } this.arrivals.delete(arrivalId); } } this.planetArrivalIds.set(planetId, []); } public planetLevelFromHexPerlin(hex: LocationId, perlin: number): PlanetLevel { const spaceType = this.spaceTypeFromPerlin(perlin); const levelBigInt = getBytesFromHex(hex, 4, 7); let ret = MIN_PLANET_LEVEL; for (let type = MAX_PLANET_LEVEL; type >= MIN_PLANET_LEVEL; type--) { if (levelBigInt < bigInt(this.contractConstants.planetLevelThresholds[type])) { ret = type; break; } } if (spaceType === SpaceType.NEBULA && ret > PlanetLevel.FOUR) { ret = PlanetLevel.FOUR; } if (spaceType === SpaceType.SPACE && ret > PlanetLevel.FIVE) { ret = PlanetLevel.FIVE; } if (ret > this.contractConstants.MAX_NATURAL_PLANET_LEVEL) { ret = this.contractConstants.MAX_NATURAL_PLANET_LEVEL as PlanetLevel; } return ret; } public spaceTypeFromPerlin(perlin: number): SpaceType { if (perlin < this.contractConstants.PERLIN_THRESHOLD_1) { return SpaceType.NEBULA; } else if (perlin < this.contractConstants.PERLIN_THRESHOLD_2) { return SpaceType.SPACE; } else if (perlin < this.contractConstants.PERLIN_THRESHOLD_3) { return SpaceType.DEEP_SPACE; } else { return SpaceType.DEAD_SPACE; } } public static getSilverNeeded(planet: Planet): number { const totalLevel = planet.upgradeState.reduce((a, b) => a + b); return (totalLevel + 1) * 0.2 * planet.silverCap; } public static planetCanUpgrade(planet: Planet): boolean { const totalRank = planet.upgradeState.reduce((a, b) => a + b); if (planet.spaceType === SpaceType.NEBULA && totalRank >= 3) return false; if (planet.spaceType === SpaceType.SPACE && totalRank >= 4) return false; if (planet.spaceType === SpaceType.DEEP_SPACE && totalRank >= 5) return false; if (planet.spaceType === SpaceType.DEAD_SPACE && totalRank >= 5) return false; return ( planet.planetLevel !== 0 && planet.planetType === PlanetType.PLANET && planet.silver >= this.getSilverNeeded(planet) ); } public planetTypeFromHexPerlin(hex: LocationId, perlin: number): PlanetType { // level must be sufficient - too low level planets have 0 silver growth const planetLevel = this.planetLevelFromHexPerlin(hex, perlin); const spaceType = this.spaceTypeFromPerlin(perlin); const weights = this.contractConstants.PLANET_TYPE_WEIGHTS[spaceType][planetLevel]; const weightSum = weights.reduce((x, y) => x + y); let thresholds = [weightSum - weights[0]]; for (let i = 1; i < weights.length; i++) { thresholds.push(thresholds[i - 1] - weights[i]); } thresholds = thresholds.map((x) => Math.floor((x * 256) / weightSum)); const typeByte = Number(getBytesFromHex(hex, 8, 9)); for (let i = 0; i < thresholds.length; i++) { if (typeByte >= thresholds[i]) { return i as PlanetType; } } // this should never happen return PlanetType.PLANET; } private getBiome(loc: WorldLocation): Biome { const { perlin, biomebase } = loc; const spaceType = this.spaceTypeFromPerlin(perlin); if (spaceType === SpaceType.DEAD_SPACE) return Biome.CORRUPTED; let biome = 3 * spaceType; if (biomebase < this.contractConstants.BIOME_THRESHOLD_1) biome += 1; else if (biomebase < this.contractConstants.BIOME_THRESHOLD_2) biome += 2; else biome += 3; return biome as Biome; } /** * returns the data for an unowned, untouched planet at location * most planets in the game are untouched and not stored in the contract, * so we need to generate their data optimistically in the client */ private defaultPlanetFromLocation(location: WorldLocation): LocatablePlanet { const { perlin } = location; const hex = location.hash; const planetLevel = this.planetLevelFromHexPerlin(hex, perlin); const planetType = this.planetTypeFromHexPerlin(hex, perlin); const spaceType = this.spaceTypeFromPerlin(perlin); const [energyCapBonus, energyGroBonus, rangeBonus, speedBonus, defBonus, spaceJunkBonus] = bonusFromHex(hex); let energyCap = this.contractConstants.defaultPopulationCap[planetLevel]; let energyGro = this.contractConstants.defaultPopulationGrowth[planetLevel]; let range = this.contractConstants.defaultRange[planetLevel]; let speed = this.contractConstants.defaultSpeed[planetLevel]; let defense = this.contractConstants.defaultDefense[planetLevel]; let silCap = this.contractConstants.defaultSilverCap[planetLevel]; let spaceJunk = this.contractConstants.PLANET_LEVEL_JUNK[planetLevel]; let silGro = 0; if (planetType === PlanetType.SILVER_MINE) { silGro = this.contractConstants.defaultSilverGrowth[planetLevel]; } energyCap *= energyCapBonus ? 2 : 1; energyGro *= energyGroBonus ? 2 : 1; range *= rangeBonus ? 2 : 1; speed *= speedBonus ? 2 : 1; defense *= defBonus ? 2 : 1; spaceJunk = Math.floor(spaceJunk / (spaceJunkBonus ? 2 : 1)); if (spaceType === SpaceType.DEAD_SPACE) { range *= 2; speed *= 2; energyCap *= 2; energyGro *= 2; silCap *= 2; silGro *= 2; defense = Math.floor((defense * 3) / 20); } else if (spaceType === SpaceType.DEEP_SPACE) { range *= 1.5; speed *= 1.5; energyCap *= 1.5; energyGro *= 1.5; silCap *= 1.5; silGro *= 1.5; defense *= 0.25; } else if (spaceType === SpaceType.SPACE) { range *= 1.25; speed *= 1.25; energyCap *= 1.25; energyGro *= 1.25; silCap *= 1.25; silGro *= 1.25; defense *= 0.5; } // apply stat modifiers for special planet types if (planetType === PlanetType.SILVER_MINE) { silCap *= 2; defense *= 0.5; } else if (planetType === PlanetType.SILVER_BANK) { speed /= 2; silCap *= 10; energyGro = 0; energyCap *= 5; } else if (planetType === PlanetType.TRADING_POST) { defense *= 0.5; silCap *= 2; } let pirates = (energyCap * this.contractConstants.defaultBarbarianPercentage[planetLevel]) / 100; // increase pirates if (spaceType === SpaceType.DEAD_SPACE) pirates *= 20; else if (spaceType === SpaceType.DEEP_SPACE) pirates *= 10; else if (spaceType === SpaceType.SPACE) pirates *= 4; if (planetType === PlanetType.SILVER_BANK) pirates /= 2; const silver = planetType === PlanetType.SILVER_MINE ? silCap / 2 : 0; speed *= this.contractConstants.TIME_FACTOR_HUNDREDTHS / 100; energyGro *= this.contractConstants.TIME_FACTOR_HUNDREDTHS / 100; silGro *= this.contractConstants.TIME_FACTOR_HUNDREDTHS / 100; const biome = this.getBiome(location); return { locationId: hex, perlin, spaceType, owner: EMPTY_ADDRESS, hatLevel: 0, bonus: bonusFromHex(hex), planetLevel, planetType, isHomePlanet: false, energyCap: energyCap, energyGrowth: energyGro, silverCap: silCap, silverGrowth: silGro, range, speed, defense, energy: pirates, silver, spaceJunk, lastUpdated: Math.floor(Date.now() / 1000), upgradeState: [0, 0, 0], transactions: new TxCollection(), unconfirmedClearEmoji: false, unconfirmedAddEmoji: false, loadingServerState: false, silverSpent: 0, prospectedBlockNumber: undefined, heldArtifactIds: [], destroyed: false, isInContract: this.touchedPlanetIds.has(hex), syncedWithContract: false, needsServerRefresh: false, coordsRevealed: false, location, biome, hasTriedFindingArtifact: false, messages: undefined, pausers: 0, invader: EMPTY_ADDRESS, capturer: EMPTY_ADDRESS, }; } private updatePlanetIfStale(planet: Planet): void { const now = Date.now(); if (now / 1000 - planet.lastUpdated > 1) { updatePlanetToTime( planet, this.getPlanetArtifacts(planet.locationId), now, this.contractConstants, this.setPlanet ); } } /** * returns timestamp (seconds) that planet will reach percent% of energycap * time may be in the past */ public getEnergyCurveAtPercent(planet: Planet, percent: number): number { const p1 = (percent / 100) * planet.energyCap; const c = planet.energyCap; const p0 = planet.energy; const g = planet.energyGrowth; const t0 = planet.lastUpdated; const t1 = (c / (4 * g)) * Math.log((p1 * (c - p0)) / (p0 * (c - p1))) + t0; return t1; } /** * returns timestamp (seconds) that planet will reach percent% of silcap if * doesn't produce silver, returns undefined if already over percent% of silcap, * returns undefined */ public getSilverCurveAtPercent(planet: Planet, percent: number): number | undefined { if (planet.silverGrowth <= 0) { return undefined; } const silverTarget = (percent / 100) * planet.silverCap; const silverDiff = silverTarget - planet.silver; if (silverDiff <= 0) { return undefined; } let timeToTarget = 0; timeToTarget += silverDiff / planet.silverGrowth; return planet.lastUpdated + timeToTarget; } /** * Returns the EthAddress of the player who can control the owner: * if the artifact is on a planet, this is the owner of the planet * if the artifact is on a voyage, this is the initiator of the voyage * if the artifact is not on either, then it is the owner of the artifact NFT */ public getArtifactController(artifactId: ArtifactId): EthAddress | undefined { const artifact = this.getArtifactById(artifactId); if (!artifact) { return undefined; } if (artifact.onPlanetId) { const planet = this.getPlanetWithId(artifact.onPlanetId); if (!planet) { return undefined; } return planet.owner === EMPTY_ADDRESS ? undefined : planet.owner; } else if (artifact.onVoyageId) { const arrival = this.arrivals.get(artifact.onVoyageId); return arrival?.arrivalData.player || undefined; } else { return artifact.currentOwner === EMPTY_ADDRESS ? undefined : artifact.currentOwner; } } /** * Get all of the incoming voyages for a given location. */ public getArrivalIdsForLocation(location: LocationId | undefined): VoyageId[] | undefined { if (!location) return []; return this.planetArrivalIds.get(location); } /** * Whether or not we're already asking the game to give us spaceships. */ public isGettingSpaceships(): boolean { return this.transactions.hasTransaction(isUnconfirmedGetShipsTx); } } ================================================ FILE: src/Backend/GameLogic/GameUIManager.ts ================================================ import { EMPTY_ADDRESS } from '@darkforest_eth/constants'; import { Monomitter, monomitter } from '@darkforest_eth/events'; import { biomeName, isLocatable, isSpaceShip } from '@darkforest_eth/gamelogic'; import { planetHasBonus } from '@darkforest_eth/hexgen'; import { EthConnection } from '@darkforest_eth/network'; import { GameGLManager, Renderer } from '@darkforest_eth/renderer'; import { isUnconfirmedMoveTx } from '@darkforest_eth/serde'; import { Artifact, ArtifactId, BaseRenderer, Biome, Chunk, CursorState, Diagnostics, EthAddress, LocatablePlanet, LocationId, PerlinConfig, Planet, PlanetLevel, PlanetType, Player, QueuedArrival, Rectangle, Setting, SpaceType, Transaction, UnconfirmedActivateArtifact, UnconfirmedMove, UnconfirmedUpgrade, Upgrade, UpgradeBranchName, WorldCoords, WorldLocation, Wormhole, } from '@darkforest_eth/types'; import autoBind from 'auto-bind'; import { BigNumber } from 'ethers'; import EventEmitter from 'events'; import deferred from 'p-defer'; import React from 'react'; import ModalManager from '../../Frontend/Game/ModalManager'; import NotificationManager from '../../Frontend/Game/NotificationManager'; import Viewport from '../../Frontend/Game/Viewport'; import { getObjectWithIdFromMap } from '../../Frontend/Utils/EmitterUtils'; import { listenForKeyboardEvents, unlinkKeyboardEvents } from '../../Frontend/Utils/KeyEmitters'; import { getBooleanSetting, getSetting, setBooleanSetting, } from '../../Frontend/Utils/SettingsHooks'; import UIEmitter, { UIEmitterEvent } from '../../Frontend/Utils/UIEmitter'; import { TerminalHandle } from '../../Frontend/Views/Terminal'; import { ContractConstants } from '../../_types/darkforest/api/ContractsAPITypes'; import { HashConfig } from '../../_types/global/GlobalTypes'; import { MiningPattern } from '../Miner/MiningPatterns'; import { coordsEqual } from '../Utils/Coordinates'; import GameManager, { GameManagerEvent } from './GameManager'; import { GameObjects } from './GameObjects'; import { PluginManager } from './PluginManager'; import TutorialManager, { TutorialState } from './TutorialManager'; import { ViewportEntities } from './ViewportEntities'; export const enum GameUIManagerEvent { InitializedPlayer = 'InitializedPlayer', InitializedPlayerError = 'InitializedPlayerError', } class GameUIManager extends EventEmitter { private readonly radiusMap: { [PlanetLevel: number]: number }; private readonly gameManager: GameManager; private modalManager: ModalManager; private terminal: React.MutableRefObject; /** * In order to render React on top of the game, we need to insert React nodes into an overlay * container. We keep a reference to this container, so that our React components can optionally * choose to render themselves into this overlay container using React Portals. */ private overlayContainer?: HTMLDivElement; private previousSelectedPlanetId: LocationId | undefined; private selectedPlanetId: LocationId | undefined; private selectedCoords: WorldCoords | undefined; private mouseDownOverPlanet: LocatablePlanet | undefined; private mouseDownOverCoords: WorldCoords | undefined; private mouseHoveringOverPlanet: LocatablePlanet | undefined; private mouseHoveringOverCoords: WorldCoords | undefined; private sendingPlanet: LocatablePlanet | undefined; private sendingCoords: WorldCoords | undefined; private isSending = false; private abandoning = false; private viewportEntities: ViewportEntities; /** * The Wormhole artifact requires you to choose a target planet. This value * indicates whether or not the player is currently selecting a target planet. */ private isChoosingTargetPlanet = false; private onChooseTargetPlanet?: (planet: LocatablePlanet | undefined) => void; // TODO: Remove later and just use minerLocations array private minerLocation: WorldCoords | undefined; private extraMinerLocations: WorldCoords[] = []; private forcesSending: { [key: string]: number } = {}; // this is a percentage private silverSending: { [key: string]: number } = {}; // this is a percentage private artifactSending: { [key: string]: Artifact | undefined } = {}; private plugins: PluginManager; public readonly selectedPlanetId$: Monomitter; public readonly hoverPlanetId$: Monomitter; public readonly hoverPlanet$: Monomitter; public readonly hoverArtifactId$: Monomitter; public readonly hoverArtifact$: Monomitter; public readonly myArtifacts$: Monomitter>; public readonly isSending$: Monomitter; public readonly isAbandoning$: Monomitter; private planetHoveringInRenderer = false; // lifecycle methods private constructor( gameManager: GameManager, terminalHandle: React.MutableRefObject ) { super(); this.gameManager = gameManager; this.terminal = terminalHandle; // if planets are more dense, make radii smaller // if planets are less dense, make radii larger // the default radii are tuned for a default planet rarity of 16384 const scaleFactor = Math.sqrt(this.gameManager.getPlanetRarity() / 16384); // TODO: will radii this large degrade performance? this.radiusMap = { [PlanetLevel.ZERO]: 1 * scaleFactor, [PlanetLevel.ONE]: 3 * scaleFactor, [PlanetLevel.TWO]: 9 * scaleFactor, [PlanetLevel.THREE]: 27 * scaleFactor, [PlanetLevel.FOUR]: 81 * scaleFactor, [PlanetLevel.FIVE]: 243 * scaleFactor, [PlanetLevel.SIX]: 486 * scaleFactor, [PlanetLevel.SEVEN]: 729 * scaleFactor, [PlanetLevel.EIGHT]: 972 * scaleFactor, [PlanetLevel.NINE]: 1215 * scaleFactor, } as const; this.plugins = new PluginManager(gameManager); this.selectedPlanetId$ = monomitter(true); this.hoverPlanetId$ = monomitter(); this.hoverPlanet$ = getObjectWithIdFromMap( this.getPlanetMap(), this.hoverPlanetId$, this.gameManager.getPlanetUpdated$() ); this.hoverArtifactId$ = monomitter(); this.hoverArtifact$ = getObjectWithIdFromMap( this.getArtifactMap(), this.hoverArtifactId$, this.gameManager.getArtifactUpdated$() ); this.myArtifacts$ = this.gameManager.getMyArtifactsUpdated$(); this.viewportEntities = new ViewportEntities(this.gameManager, this); this.isSending$ = monomitter(true); this.isAbandoning$ = monomitter(true); autoBind(this); } /** * Sets the overlay container. See {@link GameUIManger.overlayContainer} for more information * about what the overlay container is. */ public setOverlayContainer(randomContainer?: HTMLDivElement) { this.overlayContainer = randomContainer; } /** * Gets the overlay container. See {@link GameUIManger.overlayContainer} for more information * about what the overlay container is. */ public getOverlayContainer(): HTMLDivElement | undefined { return this.overlayContainer; } public static async create( gameManager: GameManager, terminalHandle: React.MutableRefObject ) { listenForKeyboardEvents(); const uiEmitter = UIEmitter.getInstance(); const uiManager = new GameUIManager(gameManager, terminalHandle); const modalManager = await ModalManager.create(gameManager.getChunkStore()); uiManager.setModalManager(modalManager); uiEmitter.on(UIEmitterEvent.WorldMouseDown, uiManager.onMouseDown); uiEmitter.on(UIEmitterEvent.WorldMouseClick, uiManager.onMouseClick); uiEmitter.on(UIEmitterEvent.WorldMouseMove, uiManager.onMouseMove); uiEmitter.on(UIEmitterEvent.WorldMouseUp, uiManager.onMouseUp); uiEmitter.on(UIEmitterEvent.WorldMouseOut, uiManager.onMouseOut); uiEmitter.on(UIEmitterEvent.SendInitiated, uiManager.onSendInit); uiEmitter.on(UIEmitterEvent.SendCancelled, uiManager.onSendCancel); uiEmitter.on(UIEmitterEvent.SendCompleted, uiManager.onSendComplete); gameManager.on(GameManagerEvent.PlanetUpdate, uiManager.updatePlanets); gameManager.on(GameManagerEvent.DiscoveredNewChunk, uiManager.onDiscoveredChunk); return uiManager; } public destroy(): void { unlinkKeyboardEvents(); const uiEmitter = UIEmitter.getInstance(); uiEmitter.removeListener(UIEmitterEvent.WorldMouseDown, this.onMouseDown); uiEmitter.removeListener(UIEmitterEvent.WorldMouseClick, this.onMouseClick); uiEmitter.removeListener(UIEmitterEvent.WorldMouseMove, this.onMouseMove); uiEmitter.removeListener(UIEmitterEvent.WorldMouseUp, this.onMouseUp); uiEmitter.removeListener(UIEmitterEvent.WorldMouseOut, this.onMouseOut); uiEmitter.removeListener(UIEmitterEvent.SendInitiated, this.onSendInit); uiEmitter.removeListener(UIEmitterEvent.SendCancelled, this.onSendCancel); uiEmitter.removeListener(UIEmitterEvent.SendCompleted, this.onSendComplete); this.gameManager.removeListener(GameManagerEvent.PlanetUpdate, this.updatePlanets); this.gameManager.removeListener( GameManagerEvent.InitializedPlayer, this.onEmitInitializedPlayer ); this.gameManager.removeListener( GameManagerEvent.InitializedPlayerError, this.onEmitInitializedPlayerError ); this.gameManager.removeListener(GameManagerEvent.DiscoveredNewChunk, this.onDiscoveredChunk); this.gameManager.destroy(); this.selectedPlanetId$.clear(); this.hoverArtifactId$.clear(); } public getStringSetting(setting: Setting): string | undefined { const account = this.getAccount(); const config = { contractAddress: this.getContractAddress(), account, }; return account && getSetting(config, setting); } public getBooleanSetting(setting: Setting): boolean { const account = this.getAccount(); if (!account) { return false; } const config = { contractAddress: this.getContractAddress(), account }; return getBooleanSetting(config, setting); } public getDiagnostics(): Diagnostics { return this.gameManager.getDiagnostics(); } public updateDiagnostics(updateFn: (d: Diagnostics) => void) { this.gameManager.updateDiagnostics(updateFn); } public getEthConnection(): EthConnection { return this.gameManager.getEthConnection(); } public getContractAddress(): EthAddress { return this.gameManager.getContractAddress(); } // actions public centerPlanet(planet: LocatablePlanet | undefined) { if (planet) { Viewport.getInstance().centerPlanet(planet); this.setSelectedPlanet(planet); } } public centerCoords(coords: WorldCoords) { const planet = this.gameManager.getPlanetWithCoords(coords); if (planet && isLocatable(planet)) { this.centerPlanet(planet); } else { Viewport.getInstance().centerCoords(coords); } } public centerLocationId(planetId: LocationId) { const planet = this.getPlanetWithId(planetId); if (planet && isLocatable(planet)) { this.centerPlanet(planet); } } public joinGame(beforeRetry: (e: Error) => Promise): Promise { return this.gameManager.joinGame(beforeRetry); } public addAccount(coords: WorldCoords): Promise { return this.gameManager.addAccount(coords); } public verifyTwitter(twitter: string): Promise { return this.gameManager.submitVerifyTwitter(twitter); } public disconnectTwitter(twitter: string) { return this.gameManager.submitDisconnectTwitter(twitter); } public getPluginManager(): PluginManager { return this.plugins; } public getPrivateKey(): string | undefined { return this.gameManager.getPrivateKey(); } public getMyBalance(): number { return this.gameManager.getMyBalanceEth(); } public getMyBalanceBn(): BigNumber { return this.gameManager.getMyBalance(); } public getMyBalance$(): Monomitter { return this.gameManager.getMyBalance$(); } public findArtifact(planetId: LocationId) { if (this.gameManager.isRoundOver()) { alert('This round has ended, and you can no longer find artifacts!'); return; } this.gameManager.findArtifact(planetId); } public prospectPlanet(planetId: LocationId) { if (this.gameManager.isRoundOver()) { alert('This round has ended, and you can no longer find artifacts!'); return; } this.gameManager.prospectPlanet(planetId); } public withdrawArtifact(locationId: LocationId, artifactId: ArtifactId) { this.gameManager.withdrawArtifact(locationId, artifactId); } public depositArtifact(locationId: LocationId, artifactId: ArtifactId) { this.gameManager.depositArtifact(locationId, artifactId); } public drawAllRunningPlugins(ctx: CanvasRenderingContext2D) { this.getPluginManager().drawAllRunningPlugins(ctx); } public activateArtifact(locationId: LocationId, id: ArtifactId, wormholeTo?: LocationId) { const confirmationText = `Are you sure you want to activate this artifact? ` + `You can only have one artifact active at time. After` + ` deactivation, you must wait for a long cooldown` + ` before you can activate it again. Some artifacts (bloom filter, black domain, photoid cannon) are consumed on usage.`; if (!confirm(confirmationText)) return; this.gameManager.activateArtifact(locationId, id, wormholeTo); } public deactivateArtifact(locationId: LocationId, artifactId: ArtifactId) { const confirmationText = `Are you sure you want to deactivate this artifact? ` + `After deactivation, you must wait for a long cooldown` + ` before you can activate it again. Some artifacts (planetary shields) are consumed on deactivation.`; if (!confirm(confirmationText)) return; this.gameManager.deactivateArtifact(locationId, artifactId); } public withdrawSilver(locationId: LocationId, amount: number) { const dontShowWarningStorageKey = `${this.getAccount()?.toLowerCase()}-withdrawnWarningAcked`; if (localStorage.getItem(dontShowWarningStorageKey) !== 'true') { localStorage.setItem(dontShowWarningStorageKey, 'true'); const confirmationText = `Are you sure you want withdraw this silver? Once you withdraw it, you ` + `cannot deposit it again. Your withdrawn silver amount will be added to your score. You'll only see this warning once!`; if (!confirm(confirmationText)) return; } this.gameManager.withdrawSilver(locationId, amount); } public startWormholeFrom(planet: LocatablePlanet): Promise { this.isChoosingTargetPlanet = true; this.mouseDownOverCoords = planet.location.coords; this.mouseDownOverPlanet = planet; const { resolve, promise } = deferred(); this.onChooseTargetPlanet = resolve; return promise; } public revealLocation(locationId: LocationId) { this.gameManager.revealLocation(locationId); } public getNextBroadcastAvailableTimestamp() { return this.gameManager.getNextBroadcastAvailableTimestamp(); } public timeUntilNextBroadcastAvailable() { return this.gameManager.timeUntilNextBroadcastAvailable(); } public getEnergyArrivingForMove( from: LocationId, to: LocationId | undefined, dist: number | undefined, energy: number ) { return this.gameManager.getEnergyArrivingForMove(from, to, dist, energy, this.abandoning); } getIsChoosingTargetPlanet() { return this.isChoosingTargetPlanet; } public onMouseDown(coords: WorldCoords) { if (this.sendingPlanet) return; const hoveringOverCoords = this.updateMouseHoveringOverCoords(coords); const hoveringOverPlanet = this.gameManager.getPlanetWithCoords(hoveringOverCoords); if (this.getIsChoosingTargetPlanet()) { this.isChoosingTargetPlanet = false; if (this.onChooseTargetPlanet) { this.onChooseTargetPlanet(hoveringOverPlanet as LocatablePlanet); this.mouseDownOverPlanet = undefined; this.mouseDownOverCoords = undefined; } } else { this.mouseDownOverPlanet = hoveringOverPlanet; this.mouseDownOverCoords = this.mouseHoveringOverCoords; } } public onMouseClick(_coords: WorldCoords) { if (!this.mouseDownOverPlanet && !this.mouseHoveringOverPlanet) { this.setSelectedPlanet(undefined); this.selectedCoords = undefined; } } public onMouseMove(coords: WorldCoords) { this.updateMouseHoveringOverCoords(coords); } public onMouseUp(coords: WorldCoords) { const mouseUpOverCoords = this.updateMouseHoveringOverCoords(coords); const mouseUpOverPlanet = this.gameManager.getPlanetWithCoords(mouseUpOverCoords); const mouseDownPlanet = this.getMouseDownPlanet(); const tutorialManager = TutorialManager.getInstance(this); const uiEmitter = UIEmitter.getInstance(); if (mouseUpOverPlanet) { if ( this.mouseDownOverPlanet && mouseUpOverPlanet.locationId === this.mouseDownOverPlanet.locationId ) { // select planet this.setSelectedPlanet(mouseUpOverPlanet); this.selectedCoords = mouseUpOverCoords; this.terminal.current?.println(`Selected: ${mouseUpOverPlanet.locationId}`); this.terminal.current?.println(``); } else if ( mouseDownPlanet && (mouseDownPlanet.owner === this.gameManager.getAccount() || this.isSendingShip(mouseDownPlanet.locationId)) ) { // move initiated if enough forces const from = mouseDownPlanet; const to = mouseUpOverPlanet; // TODO: the following code block needs to be in a Planet class let effectiveEnergy = from.energy; for (const unconfirmedMove of from.transactions?.getTransactions(isUnconfirmedMoveTx) ?? []) { effectiveEnergy -= unconfirmedMove.intent.forces; } const effPercent = Math.min(this.getForcesSending(from.locationId), 98); let forces = Math.floor((effectiveEnergy * effPercent) / 100); // make it so you leave one force behind if (this.isSendingShip(mouseDownPlanet.locationId)) { tutorialManager.acceptInput(TutorialState.Spaceship); forces = 0; } else if (forces >= from.energy) { forces = from.energy - 1; if (forces < 1) return; } const dist = this.gameManager.getDist(from.locationId, to.locationId); const myAtk: number = this.gameManager.getEnergyArrivingForMove( from.locationId, to.locationId, dist, forces, this.abandoning ); let effPercentSilver = this.getSilverSending(from.locationId); if ( effPercentSilver > 98 && from.planetType === PlanetType.SILVER_MINE && from.silver < from.silverCap ) { // player is trying to send 100% silver from a silver mine that is not at cap // Date.now() and block.timestamp are occasionally a bit out of sync, so clip effPercentSilver = 98; } if (myAtk > 0 || this.isSendingShip(from.locationId)) { const abandoning = this.isAbandoning(); const silver = Math.floor((from.silver * effPercentSilver) / 100); // TODO: do something like JSON.stringify(args) so we know formatting is correct this.terminal.current?.printShellLn( `df.move('${from.locationId}', '${to.locationId}', ${forces}, ${silver})` ); const artifact = this.getArtifactSending(from.locationId); this.gameManager.move( from.locationId, to.locationId, forces, silver, artifact?.id, abandoning ); tutorialManager.acceptInput(TutorialState.SendFleet); } uiEmitter.emit(UIEmitterEvent.SendCompleted, from.locationId); } this.isChoosingTargetPlanet = false; } else { uiEmitter.emit(UIEmitterEvent.SendCancelled); } this.mouseDownOverPlanet = undefined; this.mouseDownOverCoords = undefined; } public onMouseOut() { this.mouseDownOverPlanet = undefined; this.mouseDownOverCoords = undefined; this.setHoveringOverPlanet(undefined, true); this.mouseHoveringOverCoords = undefined; } public startExplore() { this.gameManager.startExplore(); } public stopExplore() { this.gameManager.stopExplore(); this.minerLocation = undefined; } public toggleExplore() { if (this.isMining()) { this?.stopExplore(); TutorialManager.getInstance(this).acceptInput(TutorialState.MinerPause); } else { this?.startExplore(); } } public toggleTargettingExplorer() { const modalManager = this.modalManager; if (modalManager.getCursorState() === CursorState.TargetingExplorer) modalManager.setCursorState(CursorState.Normal); else modalManager.setCursorState(CursorState.TargetingExplorer); } public setForcesSending(planetId: LocationId, percentage: number) { if (percentage < 0) percentage = 0; if (percentage > 100) percentage = 100; this.forcesSending[planetId] = percentage; this.gameManager.getGameObjects().forceTick(planetId); } public setSilverSending(planetId: LocationId, percentage: number) { if (percentage < 0) percentage = 0; if (percentage > 100) percentage = 100; this.silverSending[planetId] = percentage; this.gameManager.getGameObjects().forceTick(planetId); } public setSending(sending: boolean): void { this.isSending = sending; this.isSending$.publish(sending); } public setAbandoning(abandoning: boolean): void { if (!this.gameManager.getContractConstants().SPACE_JUNK_ENABLED) return; const planet = this.getSelectedPlanet(); if (planet?.isHomePlanet) return; if (this.isSendingShip(planet?.locationId)) return; // An abandon is always a send this.isSending = abandoning; this.abandoning = abandoning; this.isSending$.publish(abandoning); this.isAbandoning$.publish(abandoning); } public setArtifactSending(planetId: LocationId, artifact?: Artifact) { this.artifactSending[planetId] = artifact; if (this.isSendingShip(planetId)) { this.abandoning = false; this.isAbandoning$.publish(false); } this.gameManager.getGameObjects().forceTick(planetId); } public isOwnedByMe(planet: Planet): boolean { return planet.owner === this.gameManager.getAccount(); } public addNewChunk(chunk: Chunk) { this.gameManager.addNewChunk(chunk); } public bulkAddNewChunks(chunks: Chunk[]): Promise { return this.gameManager.bulkAddNewChunks(chunks); } // mining stuff public setMiningPattern(pattern: MiningPattern) { this.gameManager.setMiningPattern(pattern); } public getMiningPattern(): MiningPattern | undefined { return this.gameManager.getMiningPattern(); } public isMining(): boolean { return this.gameManager.isMining(); } // getters public getAccount(): EthAddress | undefined { return this.gameManager.getAccount(); } public isAdmin(): boolean { return this.gameManager.isAdmin(); } public getTwitter(address: EthAddress | undefined): string | undefined { return this.gameManager.getTwitter(address); } public getEndTimeSeconds(): number { return this.gameManager.getEndTimeSeconds(); } public isRoundOver(): boolean { return this.gameManager.isRoundOver(); } public getUpgrade(branch: UpgradeBranchName, level: number): Upgrade { return this.gameManager.getUpgrade(branch, level); } private getBiomeKey(biome: Biome) { return `${this.getAccount()}-${this.gameManager.getContractAddress()}-biome-${biome}`; } public getDiscoverBiomeName(biome: Biome): string { const item = localStorage.getItem(this.getBiomeKey(biome)); if (item === 'true') { return biomeName(biome); } return 'Undiscovered'; } public getDistCoords(from: WorldCoords, to: WorldCoords) { return this.gameManager.getDistCoords(from, to); } public discoverBiome(planet: LocatablePlanet): void { const key = this.getBiomeKey(planet.biome); const item = localStorage.getItem(key); if (item !== 'true') { const notifManager = NotificationManager.getInstance(); localStorage.setItem(key, 'true'); notifManager.foundBiome(planet); } } public getAllPlayers(): Player[] { return this.gameManager.getAllPlayers(); } public getSelectedPlanet(): LocatablePlanet | undefined { const planet = this.getPlanetWithId(this.selectedPlanetId); if (isLocatable(planet)) { return planet; } return undefined; } public getPreviousSelectedPlanet(): Planet | undefined { return this.getPlanetWithId(this.previousSelectedPlanetId); } public setSelectedId(id: LocationId): void { const planet = this.getPlanetWithId(id); if (planet && isLocatable(planet)) this.setSelectedPlanet(planet); } public setSelectedPlanet(planet: LocatablePlanet | undefined): void { this.previousSelectedPlanetId = this.selectedPlanetId; if (!planet) { const tutorialManager = TutorialManager.getInstance(this); tutorialManager.acceptInput(TutorialState.Deselect); } const uiEmitter = UIEmitter.getInstance(); this.selectedPlanetId = planet?.locationId; if (!planet) { this.selectedCoords = undefined; } else { const loc = this.getLocationOfPlanet(planet.locationId); if (!loc) this.selectedCoords = undefined; else { // loc is not undefined this.selectedCoords = loc.coords; if (coordsEqual(loc.coords, this.getHomeCoords())) { const tutorialManager = TutorialManager.getInstance(this); tutorialManager.acceptInput(TutorialState.HomePlanet); } } } uiEmitter.emit(UIEmitterEvent.GamePlanetSelected); this.selectedPlanetId$.publish(planet?.locationId); } public getSelectedCoords(): WorldCoords | undefined { return this.selectedCoords; } public getMouseDownPlanet(): LocatablePlanet | undefined { if (this.isSending && this.sendingPlanet) return this.sendingPlanet; return this.mouseDownOverPlanet; } public onSendInit(planet: LocatablePlanet | undefined): void { this.modalManager.setCursorState(CursorState.TargetingForces); this.isSending = true; this.sendingPlanet = planet; const loc = planet && this.getLocationOfPlanet(planet.locationId); this.sendingCoords = loc ? loc.coords : { x: 0, y: 0 }; } public onSendComplete(locationId: LocationId): void { this.modalManager.setCursorState(CursorState.Normal); // Set to undefined after SendComplete so it can send another one this.artifactSending[locationId] = undefined; this.sendingPlanet = undefined; // Done at the end so they clear the artifact this.setSending(false); this.setAbandoning(false); } public onSendCancel(): void { this.modalManager.setCursorState(CursorState.Normal); this.sendingPlanet = undefined; this.sendingCoords = undefined; this.setSending(false); this.setAbandoning(false); } public hasMinedChunk(chunkLocation: Rectangle): boolean { return this.gameManager.hasMinedChunk(chunkLocation); } public getChunk(chunkFootprint: Rectangle): Chunk | undefined { return this.gameManager.getChunk(chunkFootprint); } public spaceTypeFromPerlin(perlin: number): SpaceType { return this.gameManager.spaceTypeFromPerlin(perlin); } public getSpaceTypePerlin(coords: WorldCoords, floor: boolean): number { return this.gameManager.spaceTypePerlin(coords, floor); } public getBiomePerlin(coords: WorldCoords, floor: boolean): number { return this.gameManager.biomebasePerlin(coords, floor); } public onDiscoveredChunk(chunk: Chunk): void { const res = this.gameManager.getCurrentlyExploringChunk(); const account = this.getAccount(); const config = { contractAddress: this.getContractAddress(), account, }; if (res) { const { bottomLeft, sideLength } = res; this.minerLocation = { x: bottomLeft.x + sideLength / 2, y: bottomLeft.y + sideLength / 2, }; } else { this.minerLocation = undefined; } const notifManager = NotificationManager.getInstance(); for (const loc of chunk.planetLocations) { const planet = this.getPlanetWithId(loc.hash); if (!planet || !account) break; if (planet.owner === EMPTY_ADDRESS && planet.energy > 0) { if ( !this.getBooleanSetting(Setting.FoundPirates) && this.getBooleanSetting(Setting.TutorialCompleted) ) { notifManager.foundPirates(planet); setBooleanSetting(config, Setting.FoundPirates, true); } } if (planet.planetType === PlanetType.SILVER_MINE) { if ( !this.getBooleanSetting(Setting.FoundSilver) && this.getBooleanSetting(Setting.TutorialCompleted) ) { notifManager.foundSilver(planet); setBooleanSetting(config, Setting.FoundSilver, true); } } if (planet.planetType === PlanetType.SILVER_BANK) { if ( !this.getBooleanSetting(Setting.FoundSilverBank) && this.getBooleanSetting(Setting.TutorialCompleted) ) { notifManager.foundSilverBank(planet); setBooleanSetting(config, Setting.FoundSilverBank, true); } } if (planet.planetType === PlanetType.TRADING_POST) { if ( !this.getBooleanSetting(Setting.FoundTradingPost) && this.getBooleanSetting(Setting.TutorialCompleted) ) { notifManager.foundTradingPost(planet); setBooleanSetting(config, Setting.FoundTradingPost, true); } } if (planetHasBonus(planet)) { if ( !this.getBooleanSetting(Setting.FoundComet) && this.getBooleanSetting(Setting.TutorialCompleted) ) { notifManager.foundComet(planet); setBooleanSetting(config, Setting.FoundComet, true); } } if (isLocatable(planet) && planet.planetType === PlanetType.PLANET) { this.discoverBiome(planet); } if (isLocatable(planet) && planet.planetType === PlanetType.RUINS) { if ( !this.getBooleanSetting(Setting.FoundArtifact) && this.getBooleanSetting(Setting.TutorialCompleted) ) { notifManager.foundFoundry(planet); setBooleanSetting(config, Setting.FoundArtifact, true); } } } if (account !== undefined) { if (this.spaceTypeFromPerlin(chunk.perlin) === SpaceType.DEEP_SPACE) { if ( !this.getBooleanSetting(Setting.FoundDeepSpace) && this.getBooleanSetting(Setting.TutorialCompleted) ) { notifManager.foundDeepSpace(chunk); setBooleanSetting(config, Setting.FoundDeepSpace, true); } } else if (this.spaceTypeFromPerlin(chunk.perlin) === SpaceType.SPACE) { if ( !this.getBooleanSetting(Setting.FoundSpace) && this.getBooleanSetting(Setting.TutorialCompleted) ) { notifManager.foundSpace(chunk); setBooleanSetting(config, Setting.FoundSpace, true); } } else if (this.spaceTypeFromPerlin(chunk.perlin) === SpaceType.DEAD_SPACE) { if ( !this.getBooleanSetting(Setting.FoundDeepSpace) && this.getBooleanSetting(Setting.TutorialCompleted) ) { notifManager.foundDeadSpace(chunk); setBooleanSetting(config, Setting.FoundDeepSpace, true); } } } } public getMinerLocation(): WorldCoords | undefined { return this.minerLocation; } public setExtraMinerLocation(idx: number, coords: WorldCoords): void { this.extraMinerLocations[idx] = coords; } public removeExtraMinerLocation(idx: number): void { this.extraMinerLocations.splice(idx, 1); } public getAllMinerLocations(): WorldCoords[] { if (this.minerLocation) { return [this.minerLocation, ...this.extraMinerLocations]; } else { return this.extraMinerLocations; } } public getMouseDownCoords(): WorldCoords | undefined { if (this.isSending && this.sendingPlanet) return this.sendingCoords; return this.mouseDownOverCoords; } public setHoveringOverPlanet(planet: LocatablePlanet | undefined, inRenderer: boolean) { const lastHover = this.mouseHoveringOverPlanet; this.mouseHoveringOverPlanet = planet; if (lastHover?.locationId !== planet?.locationId) { this.hoverPlanetId$.publish(planet?.locationId); this.planetHoveringInRenderer = inRenderer; } } public setHoveringOverArtifact(artifactId?: ArtifactId) { this.hoverArtifactId$.publish(artifactId); this.hoverArtifact$.publish(artifactId ? this.getArtifactWithId(artifactId) : undefined); } public getHoveringOverPlanet(): Planet | undefined { return this.mouseHoveringOverPlanet; } public getHoveringOverCoords(): WorldCoords | undefined { return this.mouseHoveringOverCoords; } public isSendingForces(): boolean { return this.isSending; } /** * Percent from 0 to 100. */ public getForcesSending(planetId?: LocationId): number { const defaultSending = 50; if (!planetId) return defaultSending; if (this.isAbandoning()) return 100; if (this.isSendingShip(planetId)) return 0; const forces = this.forcesSending[planetId]; return forces ?? defaultSending; } /** * Percent from 0 to 100. */ public getSilverSending(planetId?: LocationId): number { const defaultSending = 0; if (!planetId) return defaultSending; if (this.isAbandoning()) return 100; if (this.isSendingShip(planetId)) return 0; return this.silverSending[planetId] ?? defaultSending; } public isAbandoning(): boolean { return this.abandoning; } public getArtifactSending(planetId?: LocationId): Artifact | undefined { if (!planetId) return undefined; return this.artifactSending[planetId]; } public getAbandonSpeedChangePercent(): number { const { SPACE_JUNK_ENABLED, ABANDON_SPEED_CHANGE_PERCENT } = this.contractConstants; if (SPACE_JUNK_ENABLED) { return ABANDON_SPEED_CHANGE_PERCENT; } else { return 100; } } public getAbandonRangeChangePercent(): number { const { SPACE_JUNK_ENABLED, ABANDON_RANGE_CHANGE_PERCENT } = this.contractConstants; if (SPACE_JUNK_ENABLED) { return ABANDON_RANGE_CHANGE_PERCENT; } else { return 100; } } public isSendingShip(planetId?: LocationId): boolean { if (!planetId) return false; return isSpaceShip(this.artifactSending[planetId]?.artifactType); } public isOverOwnPlanet(coords: WorldCoords): Planet | undefined { const res = this.viewportEntities.getNearestVisiblePlanet(coords); const planet: LocatablePlanet | undefined = res; if (!planet) { return undefined; } return planet.owner === this.gameManager.getAccount() ? planet : undefined; } public getMyArtifacts(): Artifact[] { return this.gameManager.getMyArtifacts(); } public getMyArtifactsNotOnPlanet(): Artifact[] { return this.getMyArtifacts().filter((a) => !a.onPlanetId); } public getPlanetWithId(planetId: LocationId | undefined): Planet | undefined { return this.gameManager.getPlanetWithId(planetId); } public getMyScore(): number | undefined { return this.gameManager.getMyScore(); } public getPlayer(address?: EthAddress): Player | undefined { return this.gameManager.getPlayer(address); } public getArtifactWithId(artifactId: ArtifactId | undefined): Artifact | undefined { return this.gameManager.getArtifactWithId(artifactId); } public getPlanetWithCoords(coords: WorldCoords | undefined): Planet | undefined { return coords && this.gameManager.getPlanetWithCoords(coords); } public getArtifactsWithIds(artifactIds?: ArtifactId[]): Array { return this.gameManager.getArtifactsWithIds(artifactIds); } public getArtifactPlanet(artifact: Artifact): Planet | undefined { if (!artifact.onPlanetId) return undefined; return this.getPlanetWithId(artifact.onPlanetId); } public getPlanetLevel(planetId: LocationId): PlanetLevel | undefined { return this.gameManager.getPlanetLevel(planetId); } public getAllOwnedPlanets(): Planet[] { return this.gameManager.getAllOwnedPlanets(); } public getAllVoyages(): QueuedArrival[] { return this.gameManager.getAllVoyages(); } public getSpeedBuff(): number { return this.gameManager.getSpeedBuff(this.abandoning); } public getRangeBuff(): number { return this.gameManager.getRangeBuff(this.abandoning); } /** * @todo delete this. now that {@link GameObjects} is publically accessible, we shouldn't need to * drill fields like this anymore. * @tutorial Plugin developers, please access fields like this with something like {@code df.getGameObjects().} * @deprecated */ public getUnconfirmedMoves(): Transaction[] { return this.gameManager.getUnconfirmedMoves(); } public getUnconfirmedUpgrades(): Transaction[] { return this.gameManager.getUnconfirmedUpgrades(); } public isCurrentlyRevealing(): boolean { return this.gameManager.getNextRevealCountdownInfo().currentlyRevealing; } public getUnconfirmedWormholeActivations(): Transaction[] { return this.gameManager.getUnconfirmedWormholeActivations(); } public getWormholes(): Iterable { return this.gameManager.getWormholes(); } public getLocationOfPlanet(planetId: LocationId): WorldLocation | undefined { return this.gameManager.getLocationOfPlanet(planetId); } public getExploredChunks(): Iterable { return this.gameManager.getExploredChunks(); } public getLocationsAndChunks() { return this.viewportEntities.getPlanetsAndChunks(); } public getCaptureZones() { return this.gameManager.getCaptureZones(); } public getCaptureZoneGenerator() { return this.gameManager.getCaptureZoneGenerator(); } public getIsHighPerfMode(): boolean { const account = this.getAccount(); if (account === undefined) { return false; } return this.getBooleanSetting(Setting.HighPerformanceRendering); } public getPlanetsInViewport(): Planet[] { return Array.from(this.viewportEntities.getPlanetsAndChunks().cachedPlanets.values()).map( (p) => p.planet ); } public getWorldRadius(): number { return this.gameManager.getWorldRadius(); } public getWorldSilver(): number { return this.gameManager.getWorldSilver(); } public getUniverseTotalEnergy(): number { return this.gameManager.getUniverseTotalEnergy(); } public getSilverOfPlayer(player: EthAddress): number { return this.gameManager.getSilverOfPlayer(player); } public getEnergyOfPlayer(player: EthAddress): number { return this.gameManager.getEnergyOfPlayer(player); } public getPlayerScore(player: EthAddress): number | undefined { return this.gameManager.getPlayerScore(player); } public upgrade(planet: Planet, branch: number): void { // TODO: do something like JSON.stringify(args) so we know formatting is correct this.terminal.current?.printShellLn(`df.upgrade('${planet.locationId}', ${branch})`); this.gameManager.upgrade(planet.locationId, branch); } public buyHat(planet: Planet): void { // TODO: do something like JSON.stringify(args) so we know formatting is correct this.terminal.current?.printShellLn(`df.buyHat('${planet.locationId}')`); this.gameManager.buyHat(planet.locationId); } // non-nullable public getHomeCoords(): WorldCoords { return this.gameManager.getHomeCoords() || { x: 0, y: 0 }; } public getHomeHash(): LocationId | undefined { return this.gameManager.getHomeHash(); } public getHomePlanet(): Planet | undefined { const homeHash = this.getHomeHash(); if (!homeHash) return undefined; return this.getPlanetWithId(homeHash); } public getRadiusOfPlanetLevel(planetRarity: PlanetLevel): number { return this.radiusMap[planetRarity]; } public getEnergyCurveAtPercent(planet: Planet, percent: number): number { return this.gameManager.getEnergyCurveAtPercent(planet, percent); } public getSilverCurveAtPercent(planet: Planet, percent: number): number | undefined { return this.gameManager.getSilverCurveAtPercent(planet, percent); } public getHashesPerSec(): number { return this.gameManager.getHashesPerSec(); } public generateVerificationTweet(twitter: string): Promise { return this.gameManager.getSignedTwitter(twitter); } public getPerlinThresholds(): [number, number, number] { return this.gameManager.getPerlinThresholds(); } public getHashConfig(): HashConfig { return this.gameManager.getHashConfig(); } public getViewport(): Viewport { return Viewport.getInstance(); } public getPlanetMap(): Map { return this.gameManager.getPlanetMap(); } public getArtifactMap(): Map { return this.gameManager.getArtifactMap(); } public getMyPlanetMap(): Map { return this.gameManager.getMyPlanetMap(); } public getMyArtifactMap(): Map { return this.gameManager.getMyArtifactMap(); } public getTerminal(): TerminalHandle | undefined { return this.terminal.current; } public get contractConstants(): ContractConstants { return this.gameManager.getContractConstants(); } public getSpaceJunkEnabled(): boolean { return this.contractConstants.SPACE_JUNK_ENABLED; } public get captureZonesEnabled(): boolean { return this.contractConstants.CAPTURE_ZONES_ENABLED; } public potentialCaptureScore(planetLevel: number): number { return this.contractConstants.CAPTURE_ZONE_PLANET_LEVEL_SCORE[planetLevel]; } public getDefaultSpaceJunkForPlanetLevel(level: number): number { return this.contractConstants.PLANET_LEVEL_JUNK[level]; } public getPerlinConfig(isBiome = false): PerlinConfig { const hashConfig = this.gameManager.getHashConfig(); const key = isBiome ? hashConfig.biomebaseKey : hashConfig.spaceTypeKey; return { key, scale: hashConfig.perlinLengthScale, mirrorX: hashConfig.perlinMirrorX, mirrorY: hashConfig.perlinMirrorY, floor: false, }; } /** * Gets a reference to the game's internal representation of the world state. Beware! Use this for * reading only, otherwise you might mess up the state of the game. You can try modifying the game * state in some way */ public getGameObjects(): GameObjects { return this.gameManager.getGameObjects(); } // internal utils private updatePlanets() { if (this.mouseDownOverPlanet) { this.mouseDownOverPlanet = this.gameManager.getPlanetWithId( this.mouseDownOverPlanet.locationId ) as LocatablePlanet; } if (this.mouseHoveringOverPlanet) { this.setHoveringOverPlanet( this.gameManager.getPlanetWithId( this.mouseHoveringOverPlanet.locationId ) as LocatablePlanet, true ); } } private updateMouseHoveringOverCoords(coords: WorldCoords): WorldCoords { // if the mouse is inside hitbox of a planet, snaps the mouse to center of planet this.mouseHoveringOverCoords = coords; let hoveringPlanet = undefined; const res = this.viewportEntities.getNearestVisiblePlanet(coords); if (res) { hoveringPlanet = res; this.mouseHoveringOverCoords = res.location.coords; } this.setHoveringOverPlanet(hoveringPlanet, true); this.mouseHoveringOverCoords = { x: Math.round(this.mouseHoveringOverCoords.x), y: Math.round(this.mouseHoveringOverCoords.y), }; return this.mouseHoveringOverCoords; } private onEmitInitializedPlayer() { this.emit(GameUIManagerEvent.InitializedPlayer); } private onEmitInitializedPlayerError(err: React.ReactNode) { this.emit(GameUIManagerEvent.InitializedPlayerError, err); } public getGameManager(): GameManager { return this.gameManager; } private setModalManager(modalManager: ModalManager) { this.modalManager = modalManager; } public getModalManager(): ModalManager { return this.modalManager; } /** * If there is a planet being hovered over, returns whether or not it's being hovered * over in the renderer. */ public getPlanetHoveringInRenderer() { return this.planetHoveringInRenderer; } public getRenderer(): Renderer | null { return Renderer.instance; } getPaused(): boolean { return this.gameManager.getPaused(); } getPaused$(): Monomitter { return this.gameManager.getPaused$(); } public getSilverScoreValue(): number { return this.contractConstants.SILVER_SCORE_VALUE; } public getArtifactPointValues() { return this.contractConstants.ARTIFACT_POINT_VALUES; } public getCaptureZonePointValues() { return this.contractConstants.CAPTURE_ZONE_PLANET_LEVEL_SCORE; } public getArtifactUpdated$() { return this.gameManager.getArtifactUpdated$(); } public getUIEmitter() { return UIEmitter.getInstance(); } /** * @returns - A wrapper class for the WebGL2RenderingContext. */ public getGlManager(): GameGLManager | null { const renderer = this.getRenderer(); if (renderer) return renderer.glManager; return null; } /** * @returns the CanvasRenderingContext2D for the game canvas. */ public get2dRenderer(): CanvasRenderingContext2D | null { const renderer = this.getRenderer(); if (renderer) return renderer.get2DRenderer(); return null; } /** * Replaces the current renderer with the passed in custom renderer and adds the renderer * to the rendering stack. The function will automatically determine which renderer it is * by the rendererType and the methods in the renderer. * @param customRenderer - a Renderer that follows one of the 23 renderer tempaltes */ public setCustomRenderer(customRenderer: BaseRenderer) { const renderer = this.getRenderer(); if (renderer) renderer.addCustomRenderer(customRenderer); } /** * This function will remove the passed in renderer from the renderering stack. If the * renderer is on top of the renderering stack the next renderer will be the one bellow it. * @param customRenderer - a Renderer that follows one of the 23 renderer tempaltes */ public disableCustomRenderer(customRenderer: BaseRenderer) { const renderer = this.getRenderer(); if (renderer) renderer.removeCustomRenderer(customRenderer); } } export default GameUIManager; ================================================ FILE: src/Backend/GameLogic/InitialGameStateDownloader.tsx ================================================ import { Artifact, ArtifactId, ClaimedCoords, LocationId, Planet, Player, QueuedArrival, RevealedCoords, VoyageId, } from '@darkforest_eth/types'; import _ from 'lodash'; import React from 'react'; import { Link } from '../../Frontend/Components/CoreUI'; import { LoadingBarHandle } from '../../Frontend/Components/TextLoadingBar'; import { DarkForestTips } from '../../Frontend/Views/DarkForestTips'; import { TerminalHandle } from '../../Frontend/Views/Terminal'; import { ContractConstants } from '../../_types/darkforest/api/ContractsAPITypes'; import { AddressTwitterMap } from '../../_types/darkforest/api/UtilityServerAPITypes'; import { tryGetAllTwitters } from '../Network/UtilityServerAPI'; import PersistentChunkStore from '../Storage/PersistentChunkStore'; import { ContractsAPI } from './ContractsAPI'; export interface InitialGameState { contractConstants: ContractConstants; players: Map; worldRadius: number; allTouchedPlanetIds: LocationId[]; allRevealedCoords: RevealedCoords[]; allClaimedCoords?: ClaimedCoords[]; pendingMoves: QueuedArrival[]; touchedAndLocatedPlanets: Map; artifactsOnVoyages: Artifact[]; myArtifacts: Artifact[]; heldArtifacts: Artifact[][]; loadedPlanets: LocationId[]; revealedCoordsMap: Map; claimedCoordsMap?: Map; planetVoyageIdMap: Map; arrivals: Map; twitters: AddressTwitterMap; paused: boolean; } export class InitialGameStateDownloader { private terminal: TerminalHandle; public constructor(terminal: TerminalHandle) { this.terminal = terminal; } private makeProgressListener(prettyEntityName: string) { const ref = React.createRef(); this.terminal.printLoadingBar(prettyEntityName, ref); this.terminal.newline(); return (percent: number) => { ref.current?.setFractionCompleted(percent); }; } async download( contractsAPI: ContractsAPI, persistentChunkStore: PersistentChunkStore ): Promise { const isDev = process.env.NODE_ENV !== 'production'; /** * In development we use the same contract address every time we deploy, * so storage is polluted with the IDs of old universes. */ const storedTouchedPlanetIds = isDev ? [] : await persistentChunkStore.getSavedTouchedPlanetIds(); const storedRevealedCoords = isDev ? [] : await persistentChunkStore.getSavedRevealedCoords(); this.terminal.printElement(); this.terminal.newline(); const planetIdsLoadingBar = this.makeProgressListener('Planet IDs'); const playersLoadingBar = this.makeProgressListener('Players'); const revealedPlanetsLoadingBar = this.makeProgressListener('Revealed Planet IDs'); const revealedPlanetsCoordsLoadingBar = this.makeProgressListener( 'Revealed Planet Coordinates' ); const pendingMovesLoadingBar = this.makeProgressListener('Pending Moves'); const planetsLoadingBar = this.makeProgressListener('Planets'); const planetsMetadataLoadingBar = this.makeProgressListener('Planet Metadatas'); const artifactsOnPlanetsLoadingBar = this.makeProgressListener('Artifacts On Planets'); const artifactsInFlightLoadingBar = this.makeProgressListener('Artifacts On Moves'); const yourArtifactsLoadingBar = this.makeProgressListener('Your Artifacts'); const contractConstants = contractsAPI.getConstants(); const worldRadius = contractsAPI.getWorldRadius(); const players = contractsAPI.getPlayers(playersLoadingBar); const arrivals: Map = new Map(); const planetVoyageIdMap: Map = new Map(); const minedChunks = Array.from(await persistentChunkStore.allChunks()); const minedPlanetIds = new Set( _.flatMap(minedChunks, (c) => c.planetLocations).map((l) => l.hash) ); const loadedTouchedPlanetIds = contractsAPI.getTouchedPlanetIds( storedTouchedPlanetIds.length, planetIdsLoadingBar ); const loadedRevealedCoords = contractsAPI.getRevealedPlanetsCoords( storedRevealedCoords.length, revealedPlanetsLoadingBar, revealedPlanetsCoordsLoadingBar ); const claimedCoordsMap = new Map(); const allTouchedPlanetIds = storedTouchedPlanetIds.concat(await loadedTouchedPlanetIds); const allRevealedCoords = storedRevealedCoords.concat(await loadedRevealedCoords); const revealedCoordsMap = new Map(); for (const revealedCoords of allRevealedCoords) { revealedCoordsMap.set(revealedCoords.hash, revealedCoords); } let planetsToLoad = allTouchedPlanetIds.filter( (id) => minedPlanetIds.has(id) || revealedCoordsMap.has(id) || claimedCoordsMap.has(id) ); const pendingMoves = await contractsAPI.getAllArrivals(planetsToLoad, pendingMovesLoadingBar); // add origin points of voyages to known planets, because we need to know origin owner to render // the shrinking / incoming circle for (const arrival of pendingMoves) { planetsToLoad.push(arrival.fromPlanet); } planetsToLoad = [...new Set(planetsToLoad)]; const touchedAndLocatedPlanets = await contractsAPI.bulkGetPlanets( planetsToLoad, planetsLoadingBar, planetsMetadataLoadingBar ); touchedAndLocatedPlanets.forEach((_planet, locId) => { if (touchedAndLocatedPlanets.has(locId)) { planetVoyageIdMap.set(locId, []); } }); for (const arrival of pendingMoves) { const voyageIds = planetVoyageIdMap.get(arrival.toPlanet); if (voyageIds) { voyageIds.push(arrival.eventId); planetVoyageIdMap.set(arrival.toPlanet, voyageIds); } arrivals.set(arrival.eventId, arrival); } const artifactIdsOnVoyages: ArtifactId[] = []; for (const arrival of pendingMoves) { if (arrival.artifactId) { artifactIdsOnVoyages.push(arrival.artifactId); } } const artifactsOnVoyages = await contractsAPI.bulkGetArtifacts( artifactIdsOnVoyages, artifactsInFlightLoadingBar ); const heldArtifacts = contractsAPI.bulkGetArtifactsOnPlanets( planetsToLoad, artifactsOnPlanetsLoadingBar ); const myArtifacts = contractsAPI.getPlayerArtifacts( contractsAPI.getAddress(), yourArtifactsLoadingBar ); const twitters = await tryGetAllTwitters(); const paused = contractsAPI.getIsPaused(); const initialState: InitialGameState = { contractConstants: await contractConstants, players: await players, worldRadius: await worldRadius, allTouchedPlanetIds, allRevealedCoords, pendingMoves, touchedAndLocatedPlanets, artifactsOnVoyages, myArtifacts: await myArtifacts, heldArtifacts: await heldArtifacts, loadedPlanets: planetsToLoad, revealedCoordsMap, claimedCoordsMap, planetVoyageIdMap, arrivals, twitters, paused: await paused, }; return initialState; } } const tips = [ 'Beware of pirates! To capture a planet with pirates, simply send an attack large enough to overcome its current energy.', <> Navigate the Dark Forest with allies (and enemies) - join the{' '} Dark Forest Discord! , 'There are many different artifact types, each with unique properties... try activating one on a planet!', 'The top 63 players get NFT rewards at the end of each v0.6 round!', "There are many different ways to enjoy Dark Forest - as long as you're having fun, you're doing it right.", 'Be careful when capturing planets - if you attack a player-owned planet, it may look like an act of war!', 'A planet can have at most one active artifact.', 'Withdrawing an artifact (via a Spacetime Rip) gives you full control of that artifact as an ERC 721 token. You can deposit artifacts you have withdrawn back into the universe via Spacetime Rips.', 'You can use plugins to enhance your capabilities by automating repetitive tasks. The top players are probably using plugins (:', 'Quasars can store lots of energy and silver, at the expense of being able to generate neither.', 'Never share your private key with anyone else!', 'Broadcasting a planet reveals its location to ALL other players!', 'You can spend silver to upgrade your planets.', 'Planets in Nebula are more difficult to capture than planets in Deep Space.', 'Some of the universe is corrupted, and contains special versions of the artifacts.', 'You can import and export maps! Be careful importing maps from others, they may contain fabricated map data.', <> If mining the universe is slow on your computer, you can try the Remote Miner plugin. Find that and other plugins on plugins.zkga.me. , "A planet can only have 6 artifacts on it at any given time. Sometimes more if you get lucky. It's the blockchain, after all.", 'A foundry must be prospected before you can attempt to find an artifact, but make sure to click "Find" before 256 blocks or it will be lost forever.', 'Defense upgrades make your planets less vulnerable to attack, Range upgrades make your voyages go further and decay less, and Speed upgrades make your voyages go much faster.', 'Wormhole artifacts reduce the effective distance between 2 planets. Try using them to link 2 planets very far apart!', 'Upon deactivation, some artifacts must cooldown for a period before they can be activated again.', 'Photoid Cannon artifacts will debuff your planet on activation, but get a massive stat boost for the first voyage from the planet after that a charging period. Photoid Cannon artifacts are destroyed upon use.', "Planetary Shield artifacts will massively boost a planet's defense, but at the cost of energy and energy growth stats. Planetary Shield artifacts are destroyed upon deactivation.", "Bloom Filter artifacts instantly set a planet's energy and silver to full, but are destroyed upon activation. Try using them on a Quasar!", 'Dark Forest exists on the blockchain, so you can play with an entirely different client if you want.', <> Writing plugins? Check out some documentation{' '} here {' '} and{' '} here . , ]; ================================================ FILE: src/Backend/GameLogic/LayeredMap.ts ================================================ import { MAX_PLANET_LEVEL, MIN_PLANET_LEVEL } from '@darkforest_eth/constants'; import { LocationId, Radii, WorldCoords, WorldLocation } from '@darkforest_eth/types'; import { Box, Circle, Point, QuadTree } from 'js-quadtree'; /** * For every point in each of the planet quadtrees, we store a pointer to the planet. */ interface PlanetPointData { locationId: LocationId; } /** * Data structure which allows us to efficiently query for "which planets between level X and X + n * (for positive n) are present in the given world rectangle", as well as, in the future, "which * chunks are visible in the vieport". */ export class LayeredMap { private perLevelPlanetQuadtrees: Map; private insertedLocations: Set; public constructor(worldRadius: number) { // add 500k so that players have the ability to mine far outside the current world radius. worldRadius += 500_000; const worldSize = new Box(-worldRadius, -worldRadius, worldRadius * 2, worldRadius * 2); this.perLevelPlanetQuadtrees = new Map(); this.insertedLocations = new Set(); for (let i = MIN_PLANET_LEVEL; i <= MAX_PLANET_LEVEL; i++) { const config = { maximumDepth: 10, removeEmptyNodes: true, }; this.perLevelPlanetQuadtrees.set(i, new QuadTree(worldSize, config)); } } /** * Records the fact that there is a planet at the given world location. */ public insertPlanet(location: WorldLocation, planetLevel: number) { if (this.insertedLocations.has(location.hash)) { return; } const quadTree = this.perLevelPlanetQuadtrees.get(planetLevel); const newPointData: PlanetPointData = { locationId: location.hash }; quadTree?.insert(new Point(location.coords.x, location.coords.y, newPointData)); this.insertedLocations.add(location.hash); } /** * Gets all the planets within the given world radius of a world location. */ public getPlanetsInCircle(coords: WorldCoords, worldRadius: number): LocationId[] { const results = []; for (const quad of this.perLevelPlanetQuadtrees.values()) { results.push( ...quad.query(new Circle(coords.x, coords.y, worldRadius)).map(this.getPointLocationId) ); } return results; } /** * Gets the ids of all the planets that are both within the given bounding box (defined by its bottom * left coordinate, width, and height) in the world and of a level that was passed in via the * `planetLevels` parameter. */ public getPlanets( worldX: number, worldY: number, worldWidth: number, worldHeight: number, planetLevels: number[], planetLevelToRadii: Map ): LocationId[] { const result: LocationId[] = []; for (const level of planetLevels) { const radiusPx = planetLevelToRadii.get(level)?.radiusWorld as number; const boundsIncrease = radiusPx * 5; const bounds = new Box( worldX - boundsIncrease, worldY - boundsIncrease, worldWidth + boundsIncrease * 2, worldHeight + boundsIncrease * 2 ); const planets = this.perLevelPlanetQuadtrees.get(level)?.query(bounds).map(this.getPointLocationId) || []; result.push(...planets); } return result; } private getPointLocationId(point: Point): LocationId { return (point.data as PlanetPointData).locationId; } } ================================================ FILE: src/Backend/GameLogic/PluginManager.tsx ================================================ import { Monomitter, monomitter } from '@darkforest_eth/events'; import { PluginId } from '@darkforest_eth/types'; import { EmbeddedPlugin, getEmbeddedPlugins } from '../Plugins/EmbeddedPluginLoader'; import { PluginProcess } from '../Plugins/PluginProcess'; import { SerializedPlugin } from '../Plugins/SerializedPlugin'; import GameManager from './GameManager'; /** * Represents book-keeping information about a running process. We keep it * separate from the process code, so that the plugin doesn't accidentally * overwrite this information. */ export class ProcessInfo { rendered = false; hasError = false; } /** * This class keeps track of all the plugins that this player has loaded * into their game. Acts as a task manager, supports all CRUD operations * for plugins, as well as instantiating and destroying running plugins. * All library operations are also persisted to IndexDB. * * Important! Does not run plugins until the user clicks 'run' somewhere in * this UI. This is important, because if someone develops a buggy plugin, * it would suck if that bricked their game. */ export class PluginManager { private gameManager: GameManager; /** * All the plugins in the player's library. Not all of the player's plugins * are running, and therefore not all exist in `pluginInstances`. * `PluginsManager` keeps this field in sync with the plugins the user has * saved in the IndexDB via {@link PersistentChunkStore} */ private pluginLibrary: SerializedPlugin[]; /** * Plugins that are currently loaded into the game, and are rendering into a modal. * `PluginsManager` makes sure that when a plugin starts executing, it is added into * `pluginInstances`, and that once a plugin is unloaded, its `.destroy()` method is called, and * that the plugin is removed from `pluginInstances`. */ private pluginProcesses: Record; /** * parallel to pluginProcesses */ private pluginProcessInfos: Record; /** * Event emitter that publishes whenever the set of plugins changes. */ public plugins$: Monomitter; public constructor(gameManager: GameManager) { this.gameManager = gameManager; this.pluginLibrary = []; this.pluginProcesses = {}; this.pluginProcessInfos = {}; this.plugins$ = monomitter(); } /** * If a plugin with the given id is running, call its `.destroy()` method, * and remove it from `pluginInstances`. Stop listening for new local plugins. */ public destroy(id: PluginId): void { if (this.pluginProcesses[id]) { try { const process = this.pluginProcesses[id]; if (process && typeof process.destroy === 'function') { // TODO: destroy should also receive the element to cleanup event handlers, etc process.destroy(); } } catch (e) { this.pluginProcessInfos[id].hasError = true; console.error('error when destroying plugin', e); } finally { delete this.pluginProcesses[id]; delete this.pluginProcessInfos[id]; } } } /** * Load all plugins from this disk into `pluginLibrary`. Insert the default * plugins into the player's library if the default plugins have never been * added before. Effectively idempotent after the first time you call it. * @param isAdmin Is an admin loading the plugins. * @param overwriteEmbeddedPlugins Reload all embedded plugins even if a local copy is found. * Useful for plugin development. */ public async load(isAdmin: boolean, overwriteEmbeddedPlugins: boolean): Promise { this.pluginLibrary = await this.gameManager.loadPlugins(); this.onNewEmbeddedPlugins(getEmbeddedPlugins(isAdmin), overwriteEmbeddedPlugins); this.notifyPluginLibraryUpdated(); } /** * Remove the given plugin both from the player's library, and kills * the plugin if it is running. */ public async deletePlugin(pluginId: PluginId): Promise { this.pluginLibrary = this.pluginLibrary.filter((p) => p.id !== pluginId); this.destroy(pluginId); await this.gameManager.savePlugins(this.pluginLibrary); this.notifyPluginLibraryUpdated(); } /** * Gets the serialized plugin with the given id from the player's plugin * library. `undefined` if no plugin exists. */ public getPluginFromLibrary(id?: PluginId): SerializedPlugin | undefined { return this.pluginLibrary.find((p) => p.id === id); } /** * 1) kills the plugin if it's running * 2) edits the plugin-library version of this plugin * 3) if a plugin was edited, save the plugin library to disk */ public overwritePlugin(newName: string, pluginCode: string, id: PluginId): void { this.destroy(id); const plugin = this.getPluginFromLibrary(id); if (plugin) { plugin.code = pluginCode; plugin.name = newName; plugin.lastEdited = new Date().getTime(); this.gameManager.savePlugins(this.pluginLibrary); } this.notifyPluginLibraryUpdated(); } /** * Reorders the current plugins. plugin ids in `newPluginIdOrder` must correspond * 1:1 to plugins in the plugin library. */ public reorderPlugins(newPluginIdOrder: string[]) { const newPluginsList = newPluginIdOrder .map((id) => this.pluginLibrary.find((p) => p.id === id)) .filter((p) => !!p) as SerializedPlugin[]; if (newPluginsList.length !== this.pluginLibrary.length) { throw new Error('to reorder the plugins, you must pass in precisely one id for each plugin'); } this.pluginLibrary = newPluginsList; this.gameManager.savePlugins(this.pluginLibrary); this.notifyPluginLibraryUpdated(); } /** * adds a new plugin into the plugin library. */ public addPluginToLibrary(id: PluginId, name: string, code: string): SerializedPlugin { const newPlugin: SerializedPlugin = { id, lastEdited: new Date().getTime(), name, code, }; this.pluginLibrary.push(newPlugin); this.gameManager.savePlugins(this.pluginLibrary); this.notifyPluginLibraryUpdated(); return PluginManager.copy(newPlugin); } /** * Either spawns the given plugin by evaluating its `pluginCode`, or * returns the already running plugin instance. If starting a plugin * throws an error then returns `undefined`. */ public async spawn(id: PluginId): Promise { if (this.pluginProcesses[id as string]) { return this.pluginProcesses[id as string]; } const plugin = this.getPluginFromLibrary(id); if (!plugin) { return; } this.pluginProcessInfos[plugin.id] = new ProcessInfo(); const moduleFile = new File([plugin.code], plugin.name, { type: 'text/javascript', lastModified: plugin.lastEdited, }); const moduleUrl = URL.createObjectURL(moduleFile); try { // The `webpackIgnore` "magic comment" is almost undocumented, but it makes // webpack skip over this dynamic `import` call so it won't be transformed into // a weird _webpack_require_dynamic_ call const { default: Plugin } = await import(/* webpackIgnore: true */ moduleUrl); if (this.pluginProcesses[id] === undefined) { // instantiate the plugin and attach it to the process list this.pluginProcesses[id] = new Plugin(); } } catch (e) { console.error(`Failed to start plugin: ${plugin.name} - Please review stack trace\n`, e); this.pluginProcessInfos[id].hasError = true; } return this.pluginProcesses[plugin.id]; } /** * If this plugin's `render` method has not been called yet, then * call it! Remembers that this plugin has been rendered. */ public async render(id: PluginId, element: HTMLDivElement): Promise { const process = await this.spawn(id); const processInfo = this.pluginProcessInfos[id]; if (process && typeof process.render === 'function' && processInfo && !processInfo.rendered) { try { // Allows a plugin render to be async which in turns allows // any method to be async since this is the entry point into it await process.render(element); processInfo.rendered = true; } catch (e) { processInfo.hasError = true; console.log('failed to render plugin', e); } } } /** * Gets all the plugins in this player's library. */ public getLibrary(): SerializedPlugin[] { return this.pluginLibrary.map(PluginManager.copy); } /** * If this process has been started, gets its info */ public getProcessInfo(id: PluginId): ProcessInfo { return PluginManager.copy(this.pluginProcessInfos[id as string]); } /** * Gets a map of all the currently running processes */ public getAllProcessInfos(): Map { const map = new Map(); for (const id of Object.getOwnPropertyNames(this.pluginProcessInfos)) { map.set(id as PluginId, PluginManager.copy(this.pluginProcessInfos[id])); } return map; } /** * For each currently running plugin, if the plugin has a 'draw' * function, then draw that plugin to the screen. */ public drawAllRunningPlugins(ctx: CanvasRenderingContext2D) { for (const plugin of this.pluginLibrary) { const processInfo = this.pluginProcessInfos[plugin.id]; const pluginInstance = this.pluginProcesses[plugin.id]; if (pluginInstance && typeof pluginInstance.draw === 'function' && !processInfo.hasError) { try { pluginInstance.draw(ctx); } catch (e) { console.log('failed to draw plugin', e); processInfo.hasError = true; } } } } private hasPlugin(plugin: EmbeddedPlugin): boolean { return this.pluginLibrary.some((p) => p.id === plugin.id); } private onNewEmbeddedPlugins(newPlugins: EmbeddedPlugin[], overwriteEmbeddedPlugins: boolean) { for (const plugin of newPlugins) { if (!this.hasPlugin(plugin)) { this.addPluginToLibrary(plugin.id, plugin.name, plugin.code); } else if (overwriteEmbeddedPlugins) { this.overwritePlugin(plugin.name, plugin.code, plugin.id); } } } private notifyPluginLibraryUpdated() { this.plugins$.publish(this.getLibrary()); } /** * To prevent users of this class from modifying our plugins library, * we return clones of the plugins. This should probably be a function * in a Utils file somewhere, but I thought I should leave a good comment * about why we return copies of the plugins from the library. */ private static copy(plugin: T): T { return JSON.parse(JSON.stringify(plugin)) as T; } } ================================================ FILE: src/Backend/GameLogic/TutorialManager.ts ================================================ import { Setting } from '@darkforest_eth/types'; import { EventEmitter } from 'events'; import NotificationManager from '../../Frontend/Game/NotificationManager'; import { setBooleanSetting } from '../../Frontend/Utils/SettingsHooks'; import GameUIManager from './GameUIManager'; export const enum TutorialManagerEvent { StateChanged = 'StateChanged', } export const enum TutorialState { None, HomePlanet, SendFleet, SpaceJunk, Spaceship, Deselect, ZoomOut, MinerMove, MinerPause, Terminal, HowToGetScore, ScoringDetails, Valhalla, AlmostCompleted, Completed, } class TutorialManager extends EventEmitter { static instance: TutorialManager; private tutorialState: TutorialState = TutorialState.None; private uiManager: GameUIManager; private constructor(uiManager: GameUIManager) { super(); this.uiManager = uiManager; } static getInstance(uiManager: GameUIManager) { if (!TutorialManager.instance) { TutorialManager.instance = new TutorialManager(uiManager); } return TutorialManager.instance; } private setTutorialState(newState: TutorialState) { this.tutorialState = newState; this.emit(TutorialManagerEvent.StateChanged, newState); if (newState === TutorialState.Completed) { const notifManager = NotificationManager.getInstance(); notifManager.welcomePlayer(); } } private advance() { let newState = Math.min(this.tutorialState + 1, TutorialState.Completed); if (this.shouldSkipState(newState)) newState++; this.setTutorialState(newState); } private shouldSkipState(state: TutorialState) { return !this.uiManager.getSpaceJunkEnabled() && state === TutorialState.SpaceJunk; } reset() { const config = { contractAddress: this.uiManager.getContractAddress(), account: this.uiManager.getAccount(), }; setBooleanSetting(config, Setting.TutorialOpen, true); this.setTutorialState(TutorialState.None); } complete() { this.setTutorialState(TutorialState.Completed); const config = { contractAddress: this.uiManager.getContractAddress(), account: this.uiManager.getAccount(), }; setBooleanSetting(config, Setting.TutorialCompleted, true); } acceptInput(state: TutorialState) { if (state === this.tutorialState) this.advance(); } } export default TutorialManager; ================================================ FILE: src/Backend/GameLogic/ViewportEntities.ts ================================================ import { MAX_PLANET_LEVEL, MIN_PLANET_LEVEL } from '@darkforest_eth/constants'; import { isLocatable } from '@darkforest_eth/gamelogic'; import { Chunk, LocatablePlanet, LocationId, PlanetLevel, PlanetRenderInfo, Radii, WorldCoords, } from '@darkforest_eth/types'; import Viewport from '../../Frontend/Game/Viewport'; import { planetLevelToAnimationSpeed, sinusoidalAnimation } from '../Utils/Animation'; import GameManager from './GameManager'; import GameUIManager from './GameUIManager'; /** * Efficiently calculates which planets are in the viewport, and allows you to find the nearest * visible planet to the mouse. */ export class ViewportEntities { private readonly gameManager: GameManager; private readonly uiManager: GameUIManager; private cachedExploredChunks: Set = new Set(); private cachedPlanets: Map = new Map(); public constructor(gameManager: GameManager, gameUIManager: GameUIManager) { this.gameManager = gameManager; this.uiManager = gameUIManager; this.startRefreshing(); } public startRefreshing() { setInterval(() => { this.loadPlanetMessages(); }, 10 * 1000); } public getPlanetsAndChunks() { this.updateLocationsAndChunks(); for (const p of this.cachedPlanets.values()) { p.planet.emojiBobAnimation?.update(); p.planet.emojiZoopAnimation?.update(); } return { chunks: this.cachedExploredChunks, cachedPlanets: this.cachedPlanets, }; } private updateLocationsAndChunks() { const viewport = Viewport.getInstance(); this.recalculateViewportPlanets(viewport); this.recalculateViewportChunks(viewport); this.uiManager.updateDiagnostics((d) => { d.visibleChunks = this.cachedExploredChunks.size; d.visiblePlanets = this.cachedPlanets.size; d.totalPlanets = this.gameManager.getGameObjects().getAllPlanetsMap().size; }); } private recalculateViewportChunks(viewport: Viewport) { if (this.uiManager.getIsHighPerfMode()) { return; } const chunks = new Set(); for (const exploredChunk of this.uiManager.getExploredChunks()) { if ( viewport.intersectsViewport(exploredChunk) && viewport.worldToCanvasDist(exploredChunk.chunkFootprint.sideLength) >= 3 ) { chunks.add(exploredChunk); } } this.cachedExploredChunks = chunks; } private async loadPlanetMessages() { const planetIds = []; for (const p of this.cachedPlanets.values()) { // by definition, only planets that are owned can have planet messages on them, so they must // also be 'in the contract' if (p.planet.isInContract) { planetIds.push(p.planet.locationId); } } this.gameManager.refreshServerPlanetStates(planetIds); } private recalculateViewportPlanets(viewport: Viewport) { const radii = this.getPlanetRadii(Viewport.getInstance()); const planetsInViewport = this.gameManager.getPlanetsInWorldRectangle( viewport.getViewportPosition().x - viewport.widthInWorldUnits / 2, viewport.getViewportPosition().y - viewport.heightInWorldUnits / 2, viewport.widthInWorldUnits, viewport.heightInWorldUnits, this.getVisiblePlanetLevels(viewport), radii, true ); const selectedPlanet = this.uiManager.getSelectedPlanet(); if (selectedPlanet && isLocatable(selectedPlanet)) { planetsInViewport.push(selectedPlanet); } this.replacePlanets(planetsInViewport); } private replacePlanets(newPlanetsInViewport: LocatablePlanet[]) { const radii = this.getPlanetRadii(Viewport.getInstance()); const planetsToRemove = new Set(Array.from(this.cachedPlanets.keys())); newPlanetsInViewport.forEach((planet: LocatablePlanet) => { planetsToRemove.delete(planet.locationId); const newPlanetInfo: PlanetRenderInfo = { planet: planet, radii: radii.get(planet.planetLevel) as Radii, }; if (!planet.emojiBobAnimation) { planet.emojiBobAnimation = sinusoidalAnimation( planetLevelToAnimationSpeed(planet.planetLevel) ); } this.cachedPlanets.set(planet.locationId, newPlanetInfo); }); for (const toRemove of planetsToRemove) { this.cachedPlanets.delete(toRemove); const planet = this.cachedPlanets.get(toRemove); if (planet) { planet.planet.emojiBobAnimation = undefined; } } } /** * Gets the planet that is closest to the given coordinates. Filters out irrelevant planets * using the `radiusMap` parameter, which specifies how close a planet must be in order to * be returned from this function, given that planet's level. Smaller planets have a smaller * radius, and larger planets have a larger radius. * * If a smaller and a larger planet are both within respective radii of coords, the smaller * planet is returned. */ public getNearestVisiblePlanet(coords: WorldCoords): LocatablePlanet | undefined { const radii = this.getPlanetRadii(Viewport.getInstance()); let bestPlanet: LocatablePlanet | undefined; for (const planetInfo of this.cachedPlanets.values()) { const planet = planetInfo.planet; const distThreshold = radii.get(planet.planetLevel)?.radiusWorld as number; if ( Math.abs(coords.x - planet.location.coords.x) <= distThreshold && Math.abs(coords.y - planet.location.coords.y) <= distThreshold ) { if (!bestPlanet || bestPlanet.planetLevel > planet.planetLevel) { bestPlanet = planet; } } } return bestPlanet; } /** * One entry per planet level - radius in screen pixels of that planet level given the current * viewport configuration, as well as the world radius. */ private getPlanetRadii(viewport: Viewport): Map { const result = new Map(); for (let i = MIN_PLANET_LEVEL; i <= MAX_PLANET_LEVEL; i++) { const radiusWorld = this.uiManager.getRadiusOfPlanetLevel(i as PlanetLevel); const radiusPixels = viewport.worldToCanvasDist(radiusWorld); result.set(i, { radiusWorld, radiusPixels }); } return result; } /** * Returns a list of planet levels which, when rendered, would result in a planet that has a size * larger than one pixel. */ private getVisiblePlanetLevels(viewport: Viewport) { const result = []; const viewportWidthPx = viewport.worldToCanvasDist(viewport.getViewportWorldWidth()); const minPlanetSize = viewportWidthPx > 40_000 ? 3 : 1; for (let i = 0; i <= MAX_PLANET_LEVEL; i++) { const radiusW = this.uiManager.getRadiusOfPlanetLevel(i as PlanetLevel); const radiusPx = viewport.worldToCanvasDist(radiusW); if (radiusPx >= minPlanetSize) { result.push(i); } } return result; } } ================================================ FILE: src/Backend/Miner/ChunkUtils.ts ================================================ import { Chunk, Rectangle, WorldCoords, WorldLocation } from '@darkforest_eth/types'; import { BucketId, ChunkId, PersistedChunk } from '../../_types/darkforest/api/ChunkStoreTypes'; /** * Deterministically assigns a bucket ID to a rectangle, based on its position and size in the * universe. This is kind of like a shitty hash function. Its purpose is to distribute chunks * roughly evenly between the buckets. */ export function getBucket(chunk: Rectangle): BucketId { const alphanumeric = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ'; let sum = (Math.floor(chunk.bottomLeft.x / chunk.sideLength) + Math.floor(chunk.bottomLeft.y / chunk.sideLength)) % alphanumeric.length; if (sum < 0) sum += alphanumeric.length; return alphanumeric[sum] as BucketId; } /** * A unique ID generated for each chunk based on its rectangle, as well as its bucket. It's the * primary key by which chunks are identified. */ export function getChunkKey(chunkLoc: Rectangle): ChunkId { return (`${getBucket(chunkLoc)},` + `${chunkLoc.sideLength},` + `${chunkLoc.bottomLeft.x},` + `${chunkLoc.bottomLeft.y}`) as ChunkId; } /** * Converts from the in-game representation of a chunk to its persisted representation. */ export function toPersistedChunk(chunk: Chunk): PersistedChunk { const planetLocations = chunk.planetLocations.map((location) => ({ x: location.coords.x, y: location.coords.y, h: location.hash, p: location.perlin, b: location.biomebase, })); return { x: chunk.chunkFootprint.bottomLeft.x, y: chunk.chunkFootprint.bottomLeft.y, s: chunk.chunkFootprint.sideLength, l: planetLocations, p: chunk.perlin, }; } /** * Converts from the persisted representation of a chunk to the in-game representation of a chunk. */ export const toExploredChunk = (chunk: PersistedChunk): Chunk => { const planetLocations = chunk.l.map((location) => ({ coords: { x: location.x, y: location.y }, hash: location.h, perlin: location.p, biomebase: location.b, })); return { chunkFootprint: { bottomLeft: { x: chunk.x, y: chunk.y }, sideLength: chunk.s, }, planetLocations, perlin: chunk.p, }; }; /** * An aligned chunk is one whose corner's coordinates are multiples of its side length, and its side * length is a power of two between {@link MIN_CHUNK_SIZE} and {@link MAX_CHUNK_SIZE} inclusive. * * "Aligned" chunks is that they can be merged into other aligned chunks. Non-aligned chunks cannot * always be merged into squares. The reason we care about merging is that merging chunks allows us * to represent more world-space using fewer chunks. This saves memory at both runtime and * storage-time. Therefore, we only store aligned chunks. * * As an example, chunks with any corner at (0, 0) are always aligned. A chunk with side length 4 is * aligned if it's on (4, 4), (8, 12), but not (4, 6). * * This function returns the other three chunks with the same side length of the given chunk, such * that the four chunks, if merged, would result in an "aligned" chunk whose side length is double * the given chunk. */ export const getSiblingLocations = (chunkLoc: Rectangle): [Rectangle, Rectangle, Rectangle] => { const doubleSideLen = 2 * chunkLoc.sideLength; const newBottomLeftX = Math.floor(chunkLoc.bottomLeft.x / doubleSideLen) * doubleSideLen; const newBottomLeftY = Math.floor(chunkLoc.bottomLeft.y / doubleSideLen) * doubleSideLen; const newBottomLeft = { x: newBottomLeftX, y: newBottomLeftY }; const siblingLocs: Rectangle[] = []; for (let i = 0; i < 2; i += 1) { for (let j = 0; j < 2; j += 1) { const x = newBottomLeft.x + i * chunkLoc.sideLength; const y = newBottomLeft.y + j * chunkLoc.sideLength; if (x === chunkLoc.bottomLeft.x && y === chunkLoc.bottomLeft.y) { continue; } siblingLocs.push({ bottomLeft: { x, y }, sideLength: chunkLoc.sideLength, }); } } return [siblingLocs[0], siblingLocs[1], siblingLocs[2]]; }; /** * Returns the unique aligned chunk (for definition of "aligned" see comment on * `getSiblingLocations`) with the given side length that contains the given point. A chunk contains * all of the points strictly inside of its bounds, as well as the bottom and left edges. This means * it does not contain points which are on its right or top edges. */ export function getChunkOfSideLengthContainingPoint( coords: WorldCoords, sideLength: number ): Rectangle { return { sideLength, bottomLeft: { x: Math.floor(coords.x / sideLength) * sideLength, y: Math.floor(coords.y / sideLength) * sideLength, }, }; } /** * At a high level, call this function to update an efficient quadtree-like store containing all of * the chunks that a player has either mined or imported in their client. * * More speecifically, adds the given new chunk to the given map of chunks. If the map of chunks * contains all of the "sibling" chunks to this new chunk, then instead of adding it, we merge the 4 * sibling chunks, and add the merged chunk to the map and remove the existing sibling chunks. This * function is recursive, which means that if the newly created merged chunk can also be merged with * its siblings, then we merge it, add the new larger chunk, and also remove the previously existing * sibling chunks. * * The maximum chunk size is represented by the `maxChunkSize` parameter (which has to be a power of * two). If no `maxChunkSize` parameter is provided, then there is no maxmimum chunk size, meaning * that chunks will be merged until no further merging is possible. * * `onAdd` and `onRemove` are called for each of the chunks that we add and remove to/from the * `existingChunks` map. `onAdd` will be called exactly once, whereas `onRemove` only ever be called * for sibling chunks that existed prior to this function being called. */ export function addToChunkMap( existingChunks: Map, newChunk: Chunk, onAdd?: (arg: Chunk) => void, onRemove?: (arg: Chunk) => void, maxChunkSize?: number ) { let sideLength = newChunk.chunkFootprint.sideLength; let chunkToAdd: Chunk = { chunkFootprint: { bottomLeft: newChunk.chunkFootprint.bottomLeft, sideLength, }, planetLocations: [...newChunk.planetLocations], perlin: newChunk.perlin, }; while (!maxChunkSize || sideLength < maxChunkSize) { const siblingLocs = getSiblingLocations(chunkToAdd.chunkFootprint); let siblingsMined = true; for (const siblingLoc of siblingLocs) { if (!existingChunks.get(getChunkKey(siblingLoc))) { siblingsMined = false; break; } } if (!siblingsMined) break; sideLength *= 2; let planetLocations: WorldLocation[] = chunkToAdd.planetLocations; let newPerlin = chunkToAdd.perlin / 4; for (const siblingLoc of siblingLocs) { const siblingKey = getChunkKey(siblingLoc); const sibling = existingChunks.get(siblingKey); if (onRemove !== undefined && sibling) { onRemove(sibling); } else { existingChunks.delete(siblingKey); } if (sibling) { planetLocations = planetLocations.concat(sibling.planetLocations); newPerlin += sibling.perlin / 4; } } const chunkFootprint = getChunkOfSideLengthContainingPoint( chunkToAdd.chunkFootprint.bottomLeft, sideLength ); chunkToAdd = { chunkFootprint, planetLocations, perlin: Math.floor(newPerlin * 1000) / 1000, }; } if (onAdd !== undefined) { onAdd(chunkToAdd); } else { existingChunks.set(getChunkKey(chunkToAdd.chunkFootprint), chunkToAdd); } } ================================================ FILE: src/Backend/Miner/MinerManager.ts ================================================ import { perlin } from '@darkforest_eth/hashing'; import { Chunk, PerlinConfig, Rectangle } from '@darkforest_eth/types'; import { EventEmitter } from 'events'; import _ from 'lodash'; import { ChunkStore } from '../../_types/darkforest/api/ChunkStoreTypes'; import { HashConfig, MinerWorkerMessage } from '../../_types/global/GlobalTypes'; import { getChunkKey } from './ChunkUtils'; import { MiningPattern } from './MiningPatterns'; export const enum MinerManagerEvent { DiscoveredNewChunk = 'DiscoveredNewChunk', } export type workerFactory = () => Worker; function defaultWorker() { return new Worker(new URL('./miner.worker.ts', import.meta.url)); } export class HomePlanetMinerChunkStore implements ChunkStore { private initPerlinMin: number; private initPerlinMax: number; private minedChunkKeys: Set; private perlinOptions: PerlinConfig; constructor(initPerlinMin: number, initPerlinMax: number, hashConfig: HashConfig) { this.initPerlinMin = initPerlinMin; this.initPerlinMax = initPerlinMax; this.minedChunkKeys = new Set(); this.perlinOptions = { key: hashConfig.spaceTypeKey, scale: hashConfig.perlinLengthScale, mirrorX: hashConfig.perlinMirrorX, mirrorY: hashConfig.perlinMirrorY, floor: false, }; } addChunk(exploredChunk: Chunk) { this.minedChunkKeys.add(getChunkKey(exploredChunk.chunkFootprint)); } hasMinedChunk(chunkFootprint: Rectangle) { // return true if this chunk mined, or if perlin value >= threshold if (this.minedChunkKeys.has(getChunkKey(chunkFootprint))) return true; const center = { x: chunkFootprint.bottomLeft.x + chunkFootprint.sideLength / 2, y: chunkFootprint.bottomLeft.y + chunkFootprint.sideLength / 2, }; const chunkPerlin = perlin(center, this.perlinOptions); if (chunkPerlin >= this.initPerlinMax || chunkPerlin < this.initPerlinMin) return true; return false; } } class MinerManager extends EventEmitter { private readonly minedChunksStore: ChunkStore; private readonly planetRarity: number; private isExploring = false; private miningPattern: MiningPattern; private workers: Worker[]; private worldRadius: number; private cores = 1; // chunks we're exploring private exploringChunk: { [chunkKey: string]: Chunk } = {}; // when we started exploring this chunk private exploringChunkStart: { [chunkKey: string]: number } = {}; private minersComplete: { [chunkKey: string]: number } = {}; private currentJobId = 0; private useMockHash: boolean; private perlinOptions: PerlinConfig; private hashConfig: HashConfig; private workerFactory: workerFactory; private constructor( minedChunksStore: ChunkStore, miningPattern: MiningPattern, worldRadius: number, planetRarity: number, hashConfig: HashConfig, useMockHash: boolean, workerFactory: workerFactory ) { super(); this.minedChunksStore = minedChunksStore; this.miningPattern = miningPattern; this.worldRadius = worldRadius; this.planetRarity = planetRarity; this.workers = []; this.hashConfig = hashConfig; this.perlinOptions = { key: hashConfig.spaceTypeKey, scale: hashConfig.perlinLengthScale, mirrorX: hashConfig.perlinMirrorX, mirrorY: hashConfig.perlinMirrorY, floor: false, }; this.workerFactory = workerFactory; this.useMockHash = useMockHash; } setMiningPattern(pattern: MiningPattern): void { this.miningPattern = pattern; if (this.isExploring) { this.stopExplore(); this.startExplore(); } } getMiningPattern(): MiningPattern { return this.miningPattern; } destroy(): void { this.workers.map((x) => x.terminate()); } static create( chunkStore: ChunkStore, miningPattern: MiningPattern, worldRadius: number, planetRarity: number, hashConfig: HashConfig, useMockHash = false, workerFactory: workerFactory = defaultWorker ): MinerManager { const minerManager = new MinerManager( chunkStore, miningPattern, worldRadius, planetRarity, hashConfig, useMockHash, workerFactory ); _.range(minerManager.cores).forEach((i) => minerManager.initWorker(i)); return minerManager; } private initWorker(index: number): void { this.workers[index] = this.workerFactory(); this.workers[index].onmessage = (e: MessageEvent) => { // worker explored a slice of a chunk const [exploredChunk, jobId] = JSON.parse(e.data) as [Chunk, number]; const chunkKey = this.chunkLocationToKey(exploredChunk.chunkFootprint, jobId); this.exploringChunk[chunkKey].planetLocations.push(...exploredChunk.planetLocations); this.minersComplete[chunkKey] += 1; if (this.minersComplete[chunkKey] === this.workers.length) { this.onDiscovered(this.exploringChunk[chunkKey], jobId); } }; } private async onDiscovered(exploredChunk: Chunk, jobId: number): Promise { const discoveredLoc = exploredChunk.chunkFootprint; const chunkKey = this.chunkLocationToKey(discoveredLoc, jobId); const miningTimeMillis = Date.now() - this.exploringChunkStart[chunkKey]; this.emit(MinerManagerEvent.DiscoveredNewChunk, exploredChunk, miningTimeMillis); delete this.exploringChunk[chunkKey]; delete this.minersComplete[chunkKey]; delete this.exploringChunkStart[chunkKey]; if (this.isExploring && this.currentJobId === jobId) { this.exploreNext(discoveredLoc, jobId); } } private exploreNext(fromChunk: Rectangle, jobId: number) { this.nextValidExploreTarget(fromChunk, jobId).then((nextChunk: Rectangle | undefined) => { if (!!nextChunk) { const nextChunkKey = this.chunkLocationToKey(nextChunk, jobId); const center = { x: nextChunk.bottomLeft.x + nextChunk.sideLength / 2, y: nextChunk.bottomLeft.y + nextChunk.sideLength / 2, }; const centerPerlin = perlin(center, this.perlinOptions); this.exploringChunk[nextChunkKey] = { chunkFootprint: nextChunk, planetLocations: [], perlin: centerPerlin, }; this.exploringChunkStart[nextChunkKey] = Date.now(); this.minersComplete[nextChunkKey] = 0; this.sendMessageToWorkers(nextChunk, jobId); } }); } public setCores(nCores: number): void { const wasMining = this.isMining(); this.stopExplore(); this.workers.map((x) => x.terminate()); this.workers = []; if (this.useMockHash) { this.cores = 1; } else { this.cores = nCores; } _.range(this.cores).forEach((i) => this.initWorker(i)); if (wasMining) { this.startExplore(); } } public startExplore(): void { // increments the current job ID if (!this.isExploring) { this.isExploring = true; this.currentJobId += 1; const jobId = this.currentJobId; this.exploreNext(this.miningPattern.fromChunk, jobId); } } public stopExplore(): void { this.isExploring = false; } public isMining(): boolean { return this.isExploring; } public getCurrentlyExploringChunk(): Rectangle | undefined { if (!this.isExploring) { return undefined; } for (const key in this.exploringChunk) { const res = this.chunkKeyToLocation(key); if (res) { const [chunkLocation, jobId] = res; if (jobId === this.currentJobId) { return chunkLocation; } } } return undefined; } public setRadius(radius: number): void { this.worldRadius = radius; } private async nextValidExploreTarget( chunkLocation: Rectangle, jobId: number ): Promise { // returns the first valid chunk equal to or after `chunk` (in the explore order of mining pattern) that hasn't been explored // async because it may take indefinitely long to find the next target. this will block UI if done sync // we use this trick to promisify: // https://stackoverflow.com/questions/10344498/best-way-to-iterate-over-an-array-without-blocking-the-ui/10344560#10344560 // this function may return undefined if user chooses to stop exploring or changes mining pattern in the middle of its resolution // so any function calling it should handle the undefined case appropriately let candidateChunk = chunkLocation; let count = 10000; while (!this.isValidExploreTarget(candidateChunk) && count > 0) { candidateChunk = this.miningPattern.nextChunk(candidateChunk); count -= 1; } // since user might have switched jobs or stopped exploring during the above loop if (!this.isExploring && jobId !== this.currentJobId) { return undefined; } if (this.isValidExploreTarget(candidateChunk)) { return candidateChunk; } return new Promise((resolve) => { setTimeout(async () => { const nextNextChunk = await this.nextValidExploreTarget(candidateChunk, jobId); resolve(nextNextChunk); }, 0); }); } private isValidExploreTarget(chunkLocation: Rectangle): boolean { const { bottomLeft, sideLength } = chunkLocation; const xCenter = bottomLeft.x + sideLength / 2; const yCenter = bottomLeft.y + sideLength / 2; const xMinAbs = Math.abs(xCenter) - sideLength / 2; const yMinAbs = Math.abs(yCenter) - sideLength / 2; const squareDist = xMinAbs ** 2 + yMinAbs ** 2; // should be inbounds, and unexplored return ( squareDist < this.worldRadius ** 2 && !this.minedChunksStore.hasMinedChunk(chunkLocation) ); } private sendMessageToWorkers(chunkToExplore: Rectangle, jobId: number): void { for (let workerIndex = 0; workerIndex < this.workers.length; workerIndex += 1) { const msg: MinerWorkerMessage = { chunkFootprint: chunkToExplore, workerIndex, totalWorkers: this.workers.length, jobId, useMockHash: this.useMockHash, ...this.hashConfig, }; this.workers[workerIndex].postMessage(JSON.stringify(msg)); } } private chunkLocationToKey(chunkLocation: Rectangle, jobId: number) { const x = chunkLocation.bottomLeft.x; const y = chunkLocation.bottomLeft.y; const sideLength = chunkLocation.sideLength; return `${x},${y},${sideLength},${jobId}`; } private chunkKeyToLocation(chunkKey: string): [Rectangle, number] | undefined { // returns chunk footprint and job id try { const [x, y, sideLength, jobId] = chunkKey.split(',').map((v) => parseInt(v)); return [ { bottomLeft: { x, y }, sideLength, }, jobId, ]; } catch (e) { console.error(`error while deserializing miner chunk key: ${e}`); return undefined; } } } export default MinerManager; ================================================ FILE: src/Backend/Miner/MiningPatterns.ts ================================================ import { Rectangle, WorldCoords } from '@darkforest_eth/types'; export const enum MiningPatternType { Home, Target, Spiral, Cone, Grid, ETH, SwissCheese, TowardsCenter, TowardsCenterV2, } export interface MiningPattern { type: MiningPatternType; fromChunk: Rectangle; nextChunk: (prevLoc: Rectangle) => Rectangle; } export class SpiralPattern implements MiningPattern { type: MiningPatternType = MiningPatternType.Spiral; fromChunk: Rectangle; chunkSideLength: number; constructor(center: WorldCoords, chunkSize: number) { const bottomLeftX = Math.floor(center.x / chunkSize) * chunkSize; const bottomLeftY = Math.floor(center.y / chunkSize) * chunkSize; const bottomLeft = { x: bottomLeftX, y: bottomLeftY }; this.fromChunk = { bottomLeft, sideLength: chunkSize, }; this.chunkSideLength = chunkSize; } nextChunk(chunk: Rectangle): Rectangle { const homeX = this.fromChunk.bottomLeft.x; const homeY = this.fromChunk.bottomLeft.y; const currX = chunk.bottomLeft.x; const currY = chunk.bottomLeft.y; const nextBottomLeft = { x: currX, y: currY }; if (currX === homeX && currY === homeY) { nextBottomLeft.y = homeY + this.chunkSideLength; } else if (currY - currX > homeY - homeX && currY + currX >= homeX + homeY) { if (currY + currX === homeX + homeY) { // break the circle nextBottomLeft.y = currY + this.chunkSideLength; } else { nextBottomLeft.x = currX + this.chunkSideLength; } } else if (currX + currY > homeX + homeY && currY - currX <= homeY - homeX) { nextBottomLeft.y = currY - this.chunkSideLength; } else if (currX + currY <= homeX + homeY && currY - currX < homeY - homeX) { nextBottomLeft.x = currX - this.chunkSideLength; } else { // if (currX + currY < homeX + homeY && currY - currX >= homeY - homeX) nextBottomLeft.y = currY + this.chunkSideLength; } return { bottomLeft: nextBottomLeft, sideLength: this.chunkSideLength, }; } } export class SwissCheesePattern implements MiningPattern { type: MiningPatternType = MiningPatternType.SwissCheese; fromChunk: Rectangle; chunkSideLength: number; constructor(center: WorldCoords, chunkSize: number) { const bottomLeftX = Math.floor(center.x / chunkSize) * chunkSize; const bottomLeftY = Math.floor(center.y / chunkSize) * chunkSize; const bottomLeft = { x: bottomLeftX, y: bottomLeftY }; this.fromChunk = { bottomLeft, sideLength: chunkSize, }; this.chunkSideLength = chunkSize; } nextChunk(chunk: Rectangle): Rectangle { const homeX = this.fromChunk.bottomLeft.x; const homeY = this.fromChunk.bottomLeft.y; const currX = chunk.bottomLeft.x; const currY = chunk.bottomLeft.y; const nextBottomLeft = { x: currX, y: currY }; if (currX === homeX && currY === homeY) { nextBottomLeft.y = homeY + this.chunkSideLength * 2; } else if (currY - currX > homeY - homeX && currY + currX >= homeX + homeY) { if (currY + currX === homeX + homeY) { // break the circle nextBottomLeft.y = currY + this.chunkSideLength * 2; } else { nextBottomLeft.x = currX + this.chunkSideLength * 2; } } else if (currX + currY > homeX + homeY && currY - currX <= homeY - homeX) { nextBottomLeft.y = currY - this.chunkSideLength * 2; } else if (currX + currY <= homeX + homeY && currY - currX < homeY - homeX) { nextBottomLeft.x = currX - this.chunkSideLength * 2; } else { // if (currX + currY < homeX + homeY && currY - currX >= homeY - homeX) nextBottomLeft.y = currY + this.chunkSideLength * 2; } return { bottomLeft: nextBottomLeft, sideLength: this.chunkSideLength, }; } } export class TowardsCenterPattern implements MiningPattern { type: MiningPatternType = MiningPatternType.TowardsCenter; fromChunk: Rectangle; chunkSideLength: number; private tipX: number; private tipY: number; private maxWidth = 1600; constructor(center: WorldCoords, chunkSize: number) { const bottomLeftX = Math.floor(center.x / chunkSize) * chunkSize; const bottomLeftY = Math.floor(center.y / chunkSize) * chunkSize; const bottomLeft = { x: bottomLeftX, y: bottomLeftY }; this.fromChunk = { bottomLeft, sideLength: chunkSize, }; if (bottomLeftX < 0) { this.tipX = bottomLeftX + chunkSize; } else { this.tipX = bottomLeftX - chunkSize; } if (bottomLeftY < 0) { this.tipY = bottomLeftY + chunkSize; } else { this.tipY = bottomLeftY - chunkSize; } this.chunkSideLength = chunkSize; } nextChunk(chunk: Rectangle): Rectangle { const homeX = this.fromChunk.bottomLeft.x; const homeY = this.fromChunk.bottomLeft.y; const currX = chunk.bottomLeft.x; const currY = chunk.bottomLeft.y; const absHomeX = Math.abs(homeX); const absHomeY = Math.abs(homeY); const absTipX = Math.abs(this.tipX); const absTipY = Math.abs(this.tipY); const absCurrX = Math.abs(currX); const absCurrY = Math.abs(currY); const endX = currX <= 0 ? Math.max(homeX, this.tipX - this.maxWidth) : Math.min(homeX, this.tipX + this.maxWidth); const nextBottomLeft = { x: currX, y: currY, }; if (currX === homeX && currY === homeY) { nextBottomLeft.x = this.tipX; nextBottomLeft.y = this.tipY; } else if (currX === this.tipX && currY === this.tipY) { if (currX < 0) { nextBottomLeft.x = currX - this.chunkSideLength; } else if (currX > 0) { nextBottomLeft.x = currX + this.chunkSideLength; } else { // Exactly 0 if (homeX < 0) { nextBottomLeft.x = currX - this.chunkSideLength; } else { nextBottomLeft.x = currX + this.chunkSideLength; } } } else if (absCurrX < absHomeX && currY === this.tipY && absCurrX - absTipX < this.maxWidth) { if (currX < 0) { nextBottomLeft.x = currX - this.chunkSideLength; } else if (currX > 0) { nextBottomLeft.x = currX + this.chunkSideLength; } else { // Exactly 0 if (homeX < 0) { nextBottomLeft.x = currX - this.chunkSideLength; } else { nextBottomLeft.x = currX + this.chunkSideLength; } } } else if (currX === endX && currY === this.tipY) { nextBottomLeft.x = this.tipX; if (currY < 0) { nextBottomLeft.y = currY - this.chunkSideLength; } else if (currY > 0) { nextBottomLeft.y = currY + this.chunkSideLength; } else { // Exactly 0 if (homeY < 0) { nextBottomLeft.y = currY - this.chunkSideLength; } else { nextBottomLeft.y = currY + this.chunkSideLength; } } } else if (currX === this.tipX && absCurrY < absHomeY && absCurrY - absTipY < this.maxWidth) { if (currY < 0) { nextBottomLeft.y = currY - this.chunkSideLength; } else if (currY > 0) { nextBottomLeft.y = currY + this.chunkSideLength; } else { // Exactly 0 if (homeY < 0) { nextBottomLeft.y = currY - this.chunkSideLength; } else { nextBottomLeft.y = currY + this.chunkSideLength; } } } else { if (this.tipX < 0) { this.tipX += this.chunkSideLength; } else if (this.tipX > 0) { this.tipX -= this.chunkSideLength; } else { this.tipX = 0; } if (this.tipY < 0) { this.tipY += this.chunkSideLength; } else if (this.tipY > 0) { this.tipY -= this.chunkSideLength; } else { this.tipY = 0; } nextBottomLeft.x = this.tipX; nextBottomLeft.y = this.tipY; } return { bottomLeft: nextBottomLeft, sideLength: this.chunkSideLength, }; } } export class TowardsCenterPatternV2 implements MiningPattern { type: MiningPatternType = MiningPatternType.TowardsCenterV2; fromChunk: Rectangle; chunkSideLength: number; private rowRadius: number; private yDominant: boolean; private slopeToCenter: number; constructor(center: WorldCoords, chunkSize: number) { const bottomLeftX = Math.floor(center.x / chunkSize) * chunkSize; const bottomLeftY = Math.floor(center.y / chunkSize) * chunkSize; const bottomLeft = { x: bottomLeftX, y: bottomLeftY }; this.fromChunk = { bottomLeft, sideLength: chunkSize, }; this.chunkSideLength = chunkSize; this.rowRadius = 5; // In chunks this.yDominant = Math.abs(bottomLeftY) > Math.abs(bottomLeftX); this.slopeToCenter = bottomLeftX === 0 ? 1 : bottomLeftY / bottomLeftX; // i.e. deltaY / deltaX } toChunk(coord: number): number { return Math.floor(coord / this.chunkSideLength) * this.chunkSideLength; } nextChunk(chunk: Rectangle): Rectangle { const homeX = this.fromChunk.bottomLeft.x; const homeY = this.fromChunk.bottomLeft.y; const currX = chunk.bottomLeft.x; const currY = chunk.bottomLeft.y; if (this.yDominant) { const centerOfRowX = Math.floor(homeX + (currY - homeY) / this.slopeToCenter); if (currX < centerOfRowX + this.chunkSideLength * (this.rowRadius - 1)) { return { bottomLeft: { x: currX + this.chunkSideLength, y: currY }, sideLength: this.chunkSideLength, }; } else { const nextCenterOfRowX = Math.floor( centerOfRowX + this.chunkSideLength / this.slopeToCenter ); return { bottomLeft: { x: this.toChunk(nextCenterOfRowX - (this.rowRadius - 1) * this.chunkSideLength), y: currY < 0 ? currY + this.chunkSideLength : currY - this.chunkSideLength, }, sideLength: this.chunkSideLength, }; } } // We are now in the X dominant case const centerOfRowY = Math.floor(homeY + (currX - homeX) * this.slopeToCenter); if (currY < centerOfRowY + this.chunkSideLength * (this.rowRadius - 1)) { return { bottomLeft: { x: currX, y: currY + this.chunkSideLength }, sideLength: this.chunkSideLength, }; } else { const nextCenterOfRowY = Math.floor(centerOfRowY + this.chunkSideLength * this.slopeToCenter); return { bottomLeft: { x: currX < 0 ? currX + this.chunkSideLength : currX - this.chunkSideLength, y: this.toChunk(nextCenterOfRowY - (this.rowRadius - 1) * this.chunkSideLength), }, sideLength: this.chunkSideLength, }; } } } ================================================ FILE: src/Backend/Miner/miner.worker.ts ================================================ import { mimcHash, perlin } from '@darkforest_eth/hashing'; import { locationIdFromBigInt } from '@darkforest_eth/serde'; import { Chunk, PerlinConfig, Rectangle, WorldLocation } from '@darkforest_eth/types'; import * as bigInt from 'big-integer'; import { BigInteger } from 'big-integer'; import { LOCATION_ID_UB } from '../../Frontend/Utils/constants'; import { MinerWorkerMessage } from '../../_types/global/GlobalTypes'; import { getPlanetLocations } from './permutation'; /* eslint-disable @typescript-eslint/no-explicit-any */ const ctx: Worker = self as any; /* eslint-enable @typescript-eslint/no-explicit-any */ const exploreChunk = ( chunkFootprint: Rectangle, workerIndex: number, totalWorkers: number, planetRarity: number, jobId: number, useFakeHash: boolean, planetHashKey: number, spaceTypeKey: number, biomebaseKey: number, perlinLengthScale: number, perlinMirrorX: boolean, perlinMirrorY: boolean ) => { const planetHashFn = mimcHash(planetHashKey); const spaceTypePerlinOpts: PerlinConfig = { key: spaceTypeKey, scale: perlinLengthScale, mirrorX: perlinMirrorX, mirrorY: perlinMirrorY, floor: true, }; const biomebasePerlinOpts: PerlinConfig = { key: biomebaseKey, scale: perlinLengthScale, mirrorX: perlinMirrorX, mirrorY: perlinMirrorY, floor: true, }; let planetLocations: WorldLocation[] = []; if (useFakeHash) { planetLocations = workerIndex > 0 ? [] : getPlanetLocations( spaceTypeKey, biomebaseKey, perlinLengthScale, perlinMirrorY, perlinMirrorY )(chunkFootprint, planetRarity); } else { const planetRarityBI: BigInteger = bigInt(planetRarity); let count = 0; const { x: bottomLeftX, y: bottomLeftY } = chunkFootprint.bottomLeft; const { sideLength } = chunkFootprint; for (let x = bottomLeftX; x < bottomLeftX + sideLength; x++) { for (let y = bottomLeftY; y < bottomLeftY + sideLength; y++) { if (count % totalWorkers === workerIndex) { const hash: BigInteger = planetHashFn(x, y); if (hash.lesser(LOCATION_ID_UB.divide(planetRarityBI))) { planetLocations.push({ coords: { x, y }, hash: locationIdFromBigInt(hash), perlin: perlin({ x, y }, spaceTypePerlinOpts), biomebase: perlin({ x, y }, biomebasePerlinOpts), }); } } count += 1; } } } const chunkCenter = { x: chunkFootprint.bottomLeft.x + chunkFootprint.sideLength / 2, y: chunkFootprint.bottomLeft.y + chunkFootprint.sideLength / 2, }; const chunkData: Chunk = { chunkFootprint, planetLocations, perlin: perlin(chunkCenter, { ...spaceTypePerlinOpts, floor: false }), }; ctx.postMessage(JSON.stringify([chunkData, jobId])); }; ctx.addEventListener('message', (e: MessageEvent) => { const exploreMessage: MinerWorkerMessage = JSON.parse(e.data) as MinerWorkerMessage; exploreChunk( exploreMessage.chunkFootprint, exploreMessage.workerIndex, exploreMessage.totalWorkers, exploreMessage.planetRarity, exploreMessage.jobId, exploreMessage.useMockHash, exploreMessage.planetHashKey, exploreMessage.spaceTypeKey, exploreMessage.biomebaseKey, exploreMessage.perlinLengthScale, exploreMessage.perlinMirrorX, exploreMessage.perlinMirrorY ); }); ================================================ FILE: src/Backend/Miner/permutation.ts ================================================ import { fakeHash, perlin, seededRandom } from '@darkforest_eth/hashing'; import { locationIdFromBigInt } from '@darkforest_eth/serde'; import { Rectangle, WorldCoords, WorldLocation } from '@darkforest_eth/types'; type IdxWithRand = { idx: number; rand: number; }; const SIZE = 65536; // we permute 256x256 grids of 256x256 mega-chunks let globalSeed = 1; const globalRandom = () => { return seededRandom(globalSeed++); }; const arr: IdxWithRand[] = []; for (let i = 0; i < SIZE; i += 1) { arr.push({ idx: i, rand: globalRandom(), }); } arr.sort((a, b) => a.rand - b.rand); const lookup = arr.map((a) => a.idx); const lookupInv = Array(SIZE).fill(0); for (let i = 0; i < SIZE; i += 1) { lookupInv[lookup[i]] = i; } // return the number in [0, n) congruent to m (mod n) const posMod = (m: number, n: number) => { const val = Math.floor(m / n) * n; return m - val; }; // permutation by lookup table const sigma = (x: number, y: number) => { const val = 256 * x + y; const idx = posMod(val, SIZE); const ret: [number, number] = [Math.floor(lookup[idx] / 256), lookup[idx] % 256]; return ret; }; const sigmaInv = (x: number, y: number) => { const val = 256 * x + y; const idx = posMod(val, SIZE); const ret: [number, number] = [Math.floor(lookupInv[idx] / 256), lookupInv[idx] % 256]; return ret; }; // cyclic permutation const cyc = (m: number, n: number) => (r: number, s: number) => { const val = posMod(256 * (r + m) + (s + n), SIZE); const ret: [number, number] = [Math.floor(val / 256), val % 256]; return ret; }; const cycInv = (m: number, n: number) => (r: number, s: number) => { return cyc(-m, -n)(r, s); }; export const getPlanetLocations = ( spaceTypeKey: number, biomebaseKey: number, perlinLengthScale: number, perlinMirrorX: boolean, perlinMirrorY: boolean ) => (chunkFootprint: Rectangle, planetRarity: number) => { // assume that the chunkFootprint is entirely contained within a 256x256 grid square const { bottomLeft, sideLength } = chunkFootprint; const { x, y } = bottomLeft; const m = Math.floor(x / 256); const n = Math.floor(y / 256); const [mPrime, nPrime] = sigma(m, n); const postImages: [number, number][] = []; for (let i = 0; i < SIZE / planetRarity; i += 1) { postImages.push([Math.floor(i / 256), i % 256]); } const preImages: [number, number][] = []; for (const postImage of postImages) { preImages.push(sigmaInv(...cycInv(mPrime, nPrime)(...sigmaInv(postImage[0], postImage[1])))); } const coords: WorldCoords[] = preImages.map((preImage) => ({ x: m * 256 + preImage[0], y: n * 256 + preImage[1], })); const locs: WorldLocation[] = coords .filter( (coords) => coords.x - bottomLeft.x < sideLength && coords.x >= bottomLeft.x && coords.y - bottomLeft.y < sideLength && coords.y >= bottomLeft.y ) .map((coords) => ({ coords, hash: locationIdFromBigInt(fakeHash(planetRarity)(coords.x, coords.y)), perlin: perlin(coords, { key: spaceTypeKey, scale: perlinLengthScale, mirrorX: perlinMirrorX, mirrorY: perlinMirrorY, floor: true, }), biomebase: perlin(coords, { key: biomebaseKey, scale: perlinLengthScale, mirrorX: perlinMirrorX, mirrorY: perlinMirrorY, floor: true, }), })); return locs; }; ================================================ FILE: src/Backend/Network/AccountManager.ts ================================================ import { address } from '@darkforest_eth/serde'; import { EthAddress } from '@darkforest_eth/types'; import { utils } from 'ethers'; import stringify from 'json-stable-stringify'; /** * Represents an account with which the user plays the game. */ export interface Account { address: EthAddress; privateKey: string; } /** * This is the key in local storage in which we keep an array of all the public addresses of the * accounts that have been imported/generated into this client. */ const ADDRESS_LOCAL_STORAGE_KEY = 'KNOWN_ADDRESSES'; /** * In-memory representation of all the accounts in this client. */ const accounts: Account[] = load(); /** * Store all of the accounts in local storage. */ function save() { localStorage.setItem( ADDRESS_LOCAL_STORAGE_KEY, stringify(accounts.map((account) => account.address)) ); for (const account of accounts) { localStorage.setItem(`skey-${account.address}`, account.privateKey); } } /** * Load all of the accounts from local storage. */ function load(): Account[] { const knownAddresses: EthAddress[] = []; const accounts: Account[] = []; // first we load the public addresses const serializedAddresses = localStorage.getItem(ADDRESS_LOCAL_STORAGE_KEY); if (serializedAddresses !== null) { const addresses = JSON.parse(serializedAddresses) as string[]; for (const addressStr of addresses) { knownAddresses.push(address(addressStr)); } } // then we load the private keys for (const addy of knownAddresses) { const skey = localStorage.getItem(`skey-${addy}`); if (skey !== null) { accounts.push({ address: addy, privateKey: skey, }); } } return accounts; } /** * Returns the list of accounts that are logged into the game. */ export function getAccounts(): Account[] { return [...accounts]; } /** * Adds the given account, and saves it to localstorage. */ export function addAccount(privateKey: string) { accounts.push({ address: address(utils.computeAddress(privateKey)), privateKey, }); save(); } ================================================ FILE: src/Backend/Network/Blockchain.ts ================================================ // These are loaded as URL paths by a webpack loader import diamondContractAbiUrl from '@darkforest_eth/contracts/abis/DarkForest.json'; import { createContract, createEthConnection, EthConnection } from '@darkforest_eth/network'; import type { Contract, providers, Wallet } from 'ethers'; /** * Loads the game contract, which is responsible for updating the state of the game. */ export async function loadDiamondContract( address: string, provider: providers.JsonRpcProvider, signer?: Wallet ): Promise { const abi = await fetch(diamondContractAbiUrl).then((r) => r.json()); return createContract(address, abi, provider, signer); } export function getEthConnection(): Promise { const isProd = process.env.NODE_ENV === 'production'; const defaultUrl = process.env.DEFAULT_RPC as string; let url: string; if (isProd) { url = localStorage.getItem('XDAI_RPC_ENDPOINT_v5') || defaultUrl; } else { url = 'http://localhost:8545'; } console.log(`GAME METADATA:`); console.log(`rpc url: ${url}`); console.log(`is production: ${isProd}`); console.log(`webserver url: ${process.env.DF_WEBSERVER_URL}`); return createEthConnection(url); } ================================================ FILE: src/Backend/Network/EventLogger.ts ================================================ export const enum EventType { Transaction = 'transaction', Diagnostics = 'diagnostics', } export class EventLogger { private static augmentEvent(event: unknown, eventType: EventType) { return Object.assign(event, { df_event_type: eventType }); } logEvent(eventType: EventType, event: unknown) { if (!process.env.DF_WEBSERVER_URL) { return; } fetch(`${process.env.DF_WEBSERVER_URL}/event`, { method: 'POST', body: JSON.stringify(EventLogger.augmentEvent(event, eventType)), headers: { 'Content-Type': 'application/json', }, }).catch((err) => console.log(err)); } } export const eventLogger = new EventLogger(); ================================================ FILE: src/Backend/Network/LeaderboardApi.ts ================================================ import { Leaderboard } from '@darkforest_eth/types'; export async function loadLeaderboard(): Promise { if (!process.env.DF_WEBSERVER_URL) { return { entries: [] }; } const address = `${process.env.DF_WEBSERVER_URL}/leaderboard`; const res = await fetch(address, { method: 'GET', }); const rep = await res.json(); if (rep.error) { throw new Error(rep.error); } return rep; } ================================================ FILE: src/Backend/Network/MessageAPI.ts ================================================ import { DeleteMessagesRequest, PlanetMessageRequest, PlanetMessageResponse, PostMessageRequest, SignedMessage, } from '@darkforest_eth/types'; export async function getMessagesOnPlanets( request: PlanetMessageRequest ): Promise { if (request.planets.length === 0 || !process.env.DF_WEBSERVER_URL) { return {}; } try { const response = await fetch(`${process.env.DF_WEBSERVER_URL}/messages`, { headers: { 'Content-Type': 'application/json', }, body: JSON.stringify(request), method: 'POST', }); const responseBody = (await response.json()) as PlanetMessageResponse; if (response.status === 500) { throw new Error('failed to load messages'); } return responseBody; } catch (e) { throw e; } } export async function addMessage( request: SignedMessage> ): Promise { if (!process.env.DF_WEBSERVER_URL) { return; } try { const res = await fetch(`${process.env.DF_WEBSERVER_URL}/add-message`, { headers: { 'Content-Type': 'application/json', }, body: JSON.stringify(request), method: 'POST', }); if (res.status === 500) { throw new Error('server error'); } } catch (e) { console.log('failed to add message', request); console.log(e); } } export async function deleteMessages(request: SignedMessage): Promise { if (!process.env.DF_WEBSERVER_URL) { return; } try { const res = await fetch(`${process.env.DF_WEBSERVER_URL}/delete-messages`, { headers: { 'Content-Type': 'application/json', }, body: JSON.stringify(request), method: 'POST', }); if (res.status === 500) { throw new Error('server error'); } } catch (e) { console.log('failed delete messages', request); console.log(e); } } ================================================ FILE: src/Backend/Network/NetworkHealthApi.ts ================================================ import { NetworkHealthSummary } from '@darkforest_eth/types'; /** * The Dark Forest webserver keeps track of network health, this function loads that information * from the webserver. */ export async function loadNetworkHealth(): Promise { if (!process.env.DF_WEBSERVER_URL) { return []; } const result = await fetch(`${process.env.DF_WEBSERVER_URL}/network-health`, { method: 'POST', headers: { 'Content-Type': 'application/json', }, }).then((x) => x.json()); return result as NetworkHealthSummary; } ================================================ FILE: src/Backend/Network/UtilityServerAPI.ts ================================================ import { EthAddress, RegisterResponse, SignedMessage, WhitelistStatusResponse, } from '@darkforest_eth/types'; import * as EmailValidator from 'email-validator'; import timeout from 'p-timeout'; import { TerminalHandle } from '../../Frontend/Views/Terminal'; import { AddressTwitterMap } from '../../_types/darkforest/api/UtilityServerAPITypes'; export const enum EmailResponse { Success, Invalid, ServerError, } export const submitInterestedEmail = async (email: string): Promise => { if (!process.env.DF_WEBSERVER_URL) { return EmailResponse.ServerError; } if (!EmailValidator.validate(email)) { return EmailResponse.Invalid; } const { success } = await fetch(`${process.env.DF_WEBSERVER_URL}/email/interested`, { method: 'POST', body: JSON.stringify({ email }), headers: { 'Content-Type': 'application/json', }, }).then((x) => x.json()); return success ? EmailResponse.Success : EmailResponse.ServerError; }; export const submitUnsubscribeEmail = async (email: string): Promise => { if (!process.env.DF_WEBSERVER_URL) { return EmailResponse.ServerError; } if (!EmailValidator.validate(email)) { return EmailResponse.Invalid; } const { success } = await fetch(`${process.env.DF_WEBSERVER_URL}/email/unsubscribe`, { method: 'POST', body: JSON.stringify({ email }), headers: { 'Content-Type': 'application/json', }, }).then((x) => x.json()); return success ? EmailResponse.Success : EmailResponse.ServerError; }; export const submitPlayerEmail = async ( request?: SignedMessage<{ email: string }> ): Promise => { if (!process.env.DF_WEBSERVER_URL) { return EmailResponse.ServerError; } if (!request || !EmailValidator.validate(request.message.email)) { return EmailResponse.Invalid; } const { success } = await fetch(`${process.env.DF_WEBSERVER_URL}/email/playing`, { method: 'POST', body: JSON.stringify(request), headers: { 'Content-Type': 'application/json', }, }).then((x) => x.json()); return success ? EmailResponse.Success : EmailResponse.ServerError; }; async function sleep(timeoutMs: number) { return new Promise((resolve) => { setTimeout(() => resolve(), timeoutMs); }); } export type RegisterConfirmationResponse = { /** * If the whitelist registration is successful, * this is populated with the hash of the * transaction. */ txHash?: string; /** * If the whitelist registration is unsuccessful, * this is populated with the error message explaining * why. */ errorMessage?: string; /** * If the whitelist registration is unsuccessful, this * is true if the client is able to retry registration. */ canRetry?: boolean; }; /** * Starts the registration process for the user then * polls for success. */ export async function callRegisterAndWaitForConfirmation( key: string, address: EthAddress, terminal: React.MutableRefObject ): Promise { if (!process.env.DF_WEBSERVER_URL) { return { errorMessage: 'Cannot connect to server.', canRetry: false }; } const response = await submitWhitelistKey(key, address); if (response?.error) { return { errorMessage: response.error, canRetry: false }; } while (true) { const statusResponse = await whitelistStatus(address); if (!statusResponse) return { errorMessage: 'Cannot connect to server.', canRetry: false }; terminal.current?.newline(); if (statusResponse.failedAt) { return { errorMessage: 'Transaction failed.', canRetry: true }; } else if (statusResponse.txHash) { return { txHash: statusResponse.txHash }; } else if (statusResponse.position) { if (statusResponse.position !== '0') { terminal.current?.print('Position in queue: ' + statusResponse.position + '\n'); } else { terminal.current?.print('Position in queue: You are up next!'); } } else { terminal.current?.print('Entering queue...'); } await sleep(3000); } } export const whitelistStatus = async ( address: EthAddress ): Promise => { if (!process.env.DF_WEBSERVER_URL) { return null; } return await fetch(`${process.env.DF_WEBSERVER_URL}/whitelist/address/${address}/isWhitelisted`, { method: 'GET', headers: { 'Content-Type': 'application/json', }, }).then((x) => x.json()); }; /** * Submits a whitelist key to register the given player to the game. Returns null if there was an * error. */ export const submitWhitelistKey = async ( key: string, address: EthAddress ): Promise => { if (!process.env.DF_WEBSERVER_URL) { return null; } try { return await fetch(`${process.env.DF_WEBSERVER_URL}/whitelist/register`, { method: 'POST', body: JSON.stringify({ key, address, }), headers: { 'Content-Type': 'application/json', }, }).then((x) => x.json()); } catch (e) { console.error(`error when registering for whitelist: ${e}`); return null; } }; export const requestDevFaucet = async (address: EthAddress): Promise => { if (!process.env.DF_WEBSERVER_URL) { return false; } // TODO: Provide own env variable for this feature if (process.env.NODE_ENV === 'production') { return false; } try { const { success } = await fetch(`${process.env.DF_WEBSERVER_URL}/whitelist/faucet`, { method: 'POST', body: JSON.stringify({ address, }), headers: { 'Content-Type': 'application/json', }, }).then((x) => x.json()); return success; } catch (e) { console.error(`error when requesting drip: ${e}`); return false; } }; /** * Swallows all errors. Either loads the address to twitter map from the webserver in 5 seconds, or * returan empty map. */ export const tryGetAllTwitters = async (): Promise => { try { return await timeout(getAllTwitters(), 1000 * 5, "couldn't get twitter map"); } catch (e) {} return {}; }; export const getAllTwitters = async (): Promise => { try { const twitterMap: AddressTwitterMap = await fetch( `${process.env.DF_WEBSERVER_URL}/twitter/all-twitters` ).then((x) => x.json()); return twitterMap; } catch (e) { return {}; } }; export const verifyTwitterHandle = async ( verifyMessage: SignedMessage<{ twitter: string }> ): Promise => { try { const res = await fetch(`${process.env.DF_WEBSERVER_URL}/twitter/verify-twitter`, { method: 'POST', body: JSON.stringify({ verifyMessage, }), headers: { 'Content-Type': 'application/json', }, }).then((x) => x.json()); return res.success; } catch (e) { console.error(`error when verifying twitter handle: ${e}`); return false; } }; export const disconnectTwitter = async ( disconnectMessage: SignedMessage<{ twitter: string }> ): Promise => { try { const res = await fetch(`${process.env.DF_WEBSERVER_URL}/twitter/disconnect`, { method: 'POST', body: JSON.stringify({ disconnectMessage, }), headers: { 'Content-Type': 'application/json', }, }).then((x) => x.json()); return res.success; } catch (e) { console.error(`error when disconnecting twitter handle: ${e}`); return false; } }; ================================================ FILE: src/Backend/Plugins/EmbeddedPluginLoader.ts ================================================ import { PluginId } from '@darkforest_eth/types'; /** * This interface represents an embedded plugin, which is stored in `embedded_plugins/`. */ export interface EmbeddedPlugin { id: PluginId; name: string; code: string; } /** * Load all of the embedded plugins in the dist directory of the `embedded_plugins/` project * as Plain Text files. This means that `embedded_plugins/` can't use `import` for relative paths. */ const pluginsContext = require.context('../../../embedded_plugins/', false, /\.[jt]sx?$/); function cleanFilename(filename: string) { return filename .replace(/^\.\//, '') .replace(/[_-]/g, ' ') .replace(/\.[jt]sx?$/, ''); } export function getEmbeddedPlugins(isAdmin: boolean) { return pluginsContext .keys() .filter((filename) => { if (isAdmin) { return true; } else { return !filename.startsWith('./Admin-Controls'); } }) .map((filename) => { return { id: filename as PluginId, name: cleanFilename(filename), code: pluginsContext<{ default: string }>(filename).default, }; }); } ================================================ FILE: src/Backend/Plugins/PluginProcess.ts ================================================ /** * All plugins must conform to this interface. Provides facilities for * displaying an interactive UI, as well as references to game state, * which are set externally. */ export interface PluginProcess { new (): this; /** * If present, called once when the user clicks 'run' in the plugin * manager modal. */ render?: (into: HTMLDivElement) => void; /** * If present, called at the same framerate the the game is running at, * and allows you to draw on top of the game UI. */ draw?: (ctx: CanvasRenderingContext2D) => void; /** * Called when the plugin is unloaded. Plugins unload whenever the * plugin is edited (modified and saved, or deleted). */ destroy?: () => void; } ================================================ FILE: src/Backend/Plugins/PluginTemplate.ts ================================================ import dedent from 'ts-dedent'; export const PLUGIN_TEMPLATE = dedent` /** * Remember, you have access these globals: * 1. df - Just like the df object in your console. * 2. ui - For interacting with the game's user interface. * * Let's log these to the console when you run your plugin! */ console.log(df, ui); class Plugin { constructor() {} /** * Called when plugin is launched with the "run" button. */ async render(container) {} /** * Called when plugin modal is closed. */ destroy() {} } /** * And don't forget to export it! */ export default Plugin; `; ================================================ FILE: src/Backend/Plugins/SerializedPlugin.ts ================================================ import { PluginId } from '@darkforest_eth/types'; /** * Represents a plugin that the user has added to their game. Used * internally for storing plugins. Not used for evaluating plugins! */ export interface SerializedPlugin { /** * Unique ID, assigned at the time the plugin is first saved. */ id: PluginId; /** * This code is a javascript object that complies with the * {@link PluginProcess} interface. */ code: string; /** * Shown in the list of plugins. */ name: string; /** * {@code new Date.getTime()} at the point that this plugin was saved */ lastEdited: number; } ================================================ FILE: src/Backend/Storage/PersistentChunkStore.ts ================================================ import { Chunk, ClaimedCoords, DiagnosticUpdater, EthAddress, LocationId, ModalId, ModalPosition, PersistedTransaction, Rectangle, RevealedCoords, Transaction, WorldLocation, } from '@darkforest_eth/types'; import { IDBPDatabase, openDB } from 'idb'; import stringify from 'json-stable-stringify'; import _ from 'lodash'; import { MAX_CHUNK_SIZE } from '../../Frontend/Utils/constants'; import { ChunkId, ChunkStore, PersistedChunk } from '../../_types/darkforest/api/ChunkStoreTypes'; import { addToChunkMap, getChunkKey, getChunkOfSideLengthContainingPoint, toExploredChunk, toPersistedChunk, } from '../Miner/ChunkUtils'; import { SerializedPlugin } from '../Plugins/SerializedPlugin'; const enum ObjectStore { DEFAULT = 'default', BOARD = 'knownBoard', UNCONFIRMED_ETH_TXS = 'unminedEthTxs', PLUGINS = 'plugins', /** * Store modal positions so that we can keep modal panes open across sessions. */ MODAL_POS = 'modalPositions', } const enum DBActionType { UPDATE, DELETE, } interface DBAction { type: DBActionType; dbKey: T; dbValue?: Chunk; } type DBTx = DBAction[]; interface DebouncedFunc void> { (...args: Parameters): ReturnType | undefined; cancel(): void; } interface PersistentChunkStoreConfig { db: IDBPDatabase; contractAddress: EthAddress; account: EthAddress; } export const MODAL_POSITIONS_KEY = 'modal_positions'; class PersistentChunkStore implements ChunkStore { private diagnosticUpdater?: DiagnosticUpdater; private db: IDBPDatabase; private queuedChunkWrites: DBTx[]; private throttledSaveChunkCacheToDisk: DebouncedFunc<() => Promise>; private nUpdatesLastTwoMins = 0; // we save every 5s, unless this goes above 50 private chunkMap: Map; private confirmedTxHashes: Set; private account: EthAddress; private contractAddress: EthAddress; constructor({ db, account, contractAddress }: PersistentChunkStoreConfig) { this.db = db; this.queuedChunkWrites = []; this.confirmedTxHashes = new Set(); this.throttledSaveChunkCacheToDisk = _.throttle( this.persistQueuedChunks, 2000 // TODO ); this.chunkMap = new Map(); this.account = account; this.contractAddress = contractAddress; } destroy(): void { // no-op; we don't actually destroy the instance, we leave the db connection open in case we need it in the future } /** * NOTE! if you're creating a new object store, it will not be *added* to existing dark forest * accounts. This creation code runs once per account. Therefore, if you're adding a new object * store, and need to test it out, you must either clear the indexed db databse for this account, * or create a brand new account. */ static async create({ account, contractAddress, }: Omit): Promise { const db = await openDB(`darkforest-${contractAddress}-${account}`, 1, { upgrade(db) { db.createObjectStore(ObjectStore.DEFAULT); db.createObjectStore(ObjectStore.BOARD); db.createObjectStore(ObjectStore.UNCONFIRMED_ETH_TXS); db.createObjectStore(ObjectStore.PLUGINS); db.createObjectStore(ObjectStore.MODAL_POS); }, }); const localStorageManager = new PersistentChunkStore({ db, account, contractAddress }); await localStorageManager.loadChunks(); return localStorageManager; } public setDiagnosticUpdater(diagnosticUpdater?: DiagnosticUpdater) { this.diagnosticUpdater = diagnosticUpdater; } /** * Important! This sets the key in indexed db per account and per contract. This means the same * client can connect to multiple different dark forest contracts, with multiple different * accounts, and the persistent storage will not overwrite data that is not relevant for the * current configuration of the client. */ private async getKey( key: string, objStore: ObjectStore = ObjectStore.DEFAULT ): Promise { return await this.db.get(objStore, `${this.contractAddress}-${this.account}-${key}`); } /** * Important! This sets the key in indexed db per account and per contract. This means the same * client can connect to multiple different dark forest contracts, with multiple different * accounts, and the persistent storage will not overwrite data that is not relevant for the * current configuration of the client. */ private async setKey( key: string, value: string, objStore: ObjectStore = ObjectStore.DEFAULT ): Promise { await this.db.put(objStore, value, `${this.contractAddress}-${this.account}-${key}`); } private async removeKey(key: string, objStore: ObjectStore = ObjectStore.DEFAULT): Promise { await this.db.delete(objStore, `${this.contractAddress}-${this.account}-${key}`); } private async bulkSetKeyInCollection( updateChunkTxs: DBTx[], collection: ObjectStore ): Promise { const tx = this.db.transaction(collection, 'readwrite'); updateChunkTxs.forEach((updateChunkTx) => { updateChunkTx.forEach(({ type, dbKey: key, dbValue: value }) => { if (type === DBActionType.UPDATE) { tx.store.put(toPersistedChunk(value as Chunk), key); } else if (type === DBActionType.DELETE) { tx.store.delete(key); } }); }); await tx.done; } /** * This function loads all chunks persisted in the user's storage into the game. */ private async loadChunks(): Promise { // we can't bulk get all chunks, since idb will crash/hang // we also can't assign random non-primary keys and query on ranges // so we append a random alphanumeric character to the front of keys // and then bulk query for keys starting with 0, then 1, then 2, etc. // see the `getBucket` function in `ChunkUtils.ts` for more information. const borders = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ~'; let chunkCount = 0; for (let idx = 0; idx < borders.length - 1; idx += 1) { const bucketOfChunks = await this.db.getAll( ObjectStore.BOARD, IDBKeyRange.bound(borders[idx], borders[idx + 1], false, true) ); bucketOfChunks.forEach((chunk: PersistedChunk) => { this.addChunk(toExploredChunk(chunk), false); }); chunkCount += bucketOfChunks.length; } console.log(`loaded ${chunkCount} chunks from local storage`); } /** * Rather than saving a chunk immediately after it's mined, we queue up new chunks, and * periodically save them. This function gets all of the queued new chunks, and persists them to * indexed db. */ private async persistQueuedChunks() { const toSave = [...this.queuedChunkWrites]; // make a copy this.queuedChunkWrites = []; this.diagnosticUpdater && this.diagnosticUpdater.updateDiagnostics((d) => { d.chunkUpdates = 0; }); await this.bulkSetKeyInCollection(toSave, ObjectStore.BOARD); } /** * we keep a list rather than a single location, since client/contract can * often go out of sync on initialization - if client thinks that init * failed but is wrong, it will prompt user to initialize with new home coords, * which bricks the user's account. */ public async getHomeLocations(): Promise { const homeLocations = await this.getKey('homeLocations'); let parsed: WorldLocation[] = []; if (homeLocations) { parsed = JSON.parse(homeLocations) as WorldLocation[]; } return parsed; } public async addHomeLocation(location: WorldLocation): Promise { let locationList = await this.getHomeLocations(); if (locationList) { locationList.push(location); } else { locationList = [location]; } locationList = Array.from(new Set(locationList)); await this.setKey('homeLocations', stringify(locationList)); } public async confirmHomeLocation(location: WorldLocation): Promise { await this.setKey('homeLocations', stringify([location])); } public async getSavedTouchedPlanetIds(): Promise { const touchedPlanetIds = await this.getKey('touchedPlanetIds'); if (touchedPlanetIds) { const parsed = JSON.parse(touchedPlanetIds) as LocationId[]; return parsed; } return []; } public async getSavedRevealedCoords(): Promise { const revealedPlanetIds = await this.getKey('revealedPlanetIds'); if (revealedPlanetIds) { const parsed = JSON.parse(revealedPlanetIds); // changed the type on 6/1/21 to include revealer field // eslint-disable-next-line @typescript-eslint/no-explicit-any if (parsed.length === 0 || !(parsed[0] as any).revealer) { return []; } return parsed as RevealedCoords[]; } return []; } public async getSavedClaimedCoords(): Promise { const claimedPlanetIds = await this.getKey('claimedPlanetIds'); if (claimedPlanetIds) { const parsed = JSON.parse(claimedPlanetIds); // changed the type on 6/1/21 to include revealer field // eslint-disable-next-line @typescript-eslint/no-explicit-any if (parsed.length === 0 || !(parsed[0] as any).revealer) { return []; } return parsed as ClaimedCoords[]; } return []; } public async saveTouchedPlanetIds(ids: LocationId[]) { await this.setKey('touchedPlanetIds', stringify(ids)); } public async saveRevealedCoords(revealedCoordTups: RevealedCoords[]) { await this.setKey('revealedPlanetIds', stringify(revealedCoordTups)); } public async saveClaimedCoords(claimedCoordTupps: ClaimedCoords[]) { await this.setKey('claimedPlanetIds', stringify(claimedCoordTupps)); } /** * Returns the explored chunk data for the given rectangle if that chunk has been mined. If this * chunk is entirely contained within another bigger chunk that has been mined, return that chunk. * `chunkLoc` is an aligned square, as defined in ChunkUtils.ts in the `getSiblingLocations` * function. */ public getChunkByFootprint(chunkLoc: Rectangle): Chunk | undefined { let sideLength = chunkLoc.sideLength; while (sideLength <= MAX_CHUNK_SIZE) { const testChunkLoc = getChunkOfSideLengthContainingPoint(chunkLoc.bottomLeft, sideLength); const chunk = this.getChunkById(getChunkKey(testChunkLoc)); if (chunk) { return chunk; } sideLength *= 2; } return undefined; } public hasMinedChunk(chunkLoc: Rectangle): boolean { return !!this.getChunkByFootprint(chunkLoc); } private getChunkById(chunkId: ChunkId): Chunk | undefined { return this.chunkMap.get(chunkId); } /** * When a chunk is mined, or a chunk is imported via map import, or a chunk is loaded from * persistent storage for the first time, we need to add this chunk to the game. This function * allows you to add a new chunk to the game, and optionally persist that chunk. The reason you * might not want to persist the chunk is if you are sure that you got it from persistent storage. * i.e. it already exists in persistent storage. */ public addChunk(chunk: Chunk, persistChunk = true): void { if (this.hasMinedChunk(chunk.chunkFootprint)) { return; } const tx: DBAction[] = []; if (persistChunk) { const minedSubChunks = this.getMinedSubChunks(chunk); for (const subChunk of minedSubChunks) { tx.push({ type: DBActionType.DELETE, dbKey: getChunkKey(subChunk.chunkFootprint), }); } } addToChunkMap( this.chunkMap, chunk, (chunk) => { tx.push({ type: DBActionType.UPDATE, dbKey: getChunkKey(chunk.chunkFootprint), dbValue: chunk, }); }, (chunk) => { tx.push({ type: DBActionType.DELETE, dbKey: getChunkKey(chunk.chunkFootprint), }); }, MAX_CHUNK_SIZE ); // modify in-memory store for (const action of tx) { if (action.type === DBActionType.UPDATE && action.dbValue) { this.chunkMap.set(action.dbKey, action.dbValue); } else if (action.type === DBActionType.DELETE) { this.chunkMap.delete(action.dbKey); } } this.diagnosticUpdater?.updateDiagnostics((d) => { d.totalChunks = this.chunkMap.size; }); // can stop here, if we're just loading into in-memory store from storage if (!persistChunk) { return; } this.queuedChunkWrites.push(tx); this.diagnosticUpdater && this.diagnosticUpdater.updateDiagnostics((d) => { d.chunkUpdates = this.queuedChunkWrites.length; }); // save chunks every 5s if we're just starting up, or 30s once we're moving this.recomputeSaveThrottleAfterUpdate(); this.throttledSaveChunkCacheToDisk(); } /** * Returns all the mined chunks with smaller sidelength strictly contained in the chunk. * * TODO: move this into ChunkUtils, and also make use of it, the way that it is currently used, in * the function named `addToChunkMap`. */ private getMinedSubChunks(chunk: Chunk): Chunk[] { const ret: Chunk[] = []; for ( let clearingSideLen = 16; clearingSideLen < chunk.chunkFootprint.sideLength; clearingSideLen *= 2 ) { for (let x = 0; x < chunk.chunkFootprint.sideLength; x += clearingSideLen) { for (let y = 0; y < chunk.chunkFootprint.sideLength; y += clearingSideLen) { const queryChunk: Rectangle = { bottomLeft: { x: chunk.chunkFootprint.bottomLeft.x + x, y: chunk.chunkFootprint.bottomLeft.y + y, }, sideLength: clearingSideLen, }; const queryChunkKey = getChunkKey(queryChunk); const exploredChunk = this.getChunkById(queryChunkKey); if (exploredChunk) { ret.push(exploredChunk); } } } } return ret; } private recomputeSaveThrottleAfterUpdate() { this.nUpdatesLastTwoMins += 1; if (this.nUpdatesLastTwoMins === 50) { this.throttledSaveChunkCacheToDisk.cancel(); this.throttledSaveChunkCacheToDisk = _.throttle(this.persistQueuedChunks, 30000); } setTimeout(() => { this.nUpdatesLastTwoMins -= 1; if (this.nUpdatesLastTwoMins === 49) { this.throttledSaveChunkCacheToDisk.cancel(); this.throttledSaveChunkCacheToDisk = _.throttle(this.persistQueuedChunks, 5000); } }, 120000); } public allChunks(): Iterable { return this.chunkMap.values(); } /** * Whenever a transaction is submitted, it is persisted. When the transaction either fails or * succeeds, it is un-persisted. The reason we persist submitted transactions is to be able to * wait for them upon a fresh start of the game if you close the game before a transaction * confirms. */ public async onEthTxSubmit(tx: Transaction): Promise { // in case the tx was mined and saved already if (!tx.hash || this.confirmedTxHashes.has(tx.hash)) return; const ser: PersistedTransaction = { hash: tx.hash, intent: tx.intent }; await this.db.put(ObjectStore.UNCONFIRMED_ETH_TXS, JSON.parse(JSON.stringify(ser)), tx.hash); } /** * Partner function to {@link PersistentChunkStore#onEthTxSubmit} */ public async onEthTxComplete(txHash: string): Promise { this.confirmedTxHashes.add(txHash); await this.db.delete(ObjectStore.UNCONFIRMED_ETH_TXS, txHash); } public async getUnconfirmedSubmittedEthTxs(): Promise { const ret: PersistedTransaction[] = await this.db.getAll(ObjectStore.UNCONFIRMED_ETH_TXS); return ret; } public async loadPlugins(): Promise { const savedPlugins = await this.getKey('plugins', ObjectStore.PLUGINS); if (!savedPlugins) { return []; } return JSON.parse(savedPlugins) as SerializedPlugin[]; } public async savePlugins(plugins: SerializedPlugin[]): Promise { await this.setKey('plugins', JSON.stringify(plugins), ObjectStore.PLUGINS); } public async saveModalPositions(modalPositions: Map): Promise { if (!this.db.objectStoreNames.contains(ObjectStore.MODAL_POS)) return; const serialized = JSON.stringify(Array.from(modalPositions.entries())); await this.setKey(MODAL_POSITIONS_KEY, serialized, ObjectStore.MODAL_POS); } public async loadModalPositions(): Promise> { if (!this.db.objectStoreNames.contains(ObjectStore.MODAL_POS)) return new Map(); const winPos = await this.getKey(MODAL_POSITIONS_KEY, ObjectStore.MODAL_POS); return new Map(winPos ? JSON.parse(winPos) : null); } } export default PersistentChunkStore; ================================================ FILE: src/Backend/Storage/ReaderDataStore.ts ================================================ import { isLocatable } from '@darkforest_eth/gamelogic'; import { EthConnection } from '@darkforest_eth/network'; import { ArtifactId, Biome, EthAddress, LocatablePlanet, LocationId, Planet, SpaceType, WorldLocation, } from '@darkforest_eth/types'; import { ContractConstants } from '../../_types/darkforest/api/ContractsAPITypes'; import { AddressTwitterMap } from '../../_types/darkforest/api/UtilityServerAPITypes'; import { arrive, updatePlanetToTime } from '../GameLogic/ArrivalUtils'; import { ContractsAPI, makeContractsAPI } from '../GameLogic/ContractsAPI'; import { getAllTwitters } from '../Network/UtilityServerAPI'; import PersistentChunkStore from './PersistentChunkStore'; export const enum SinglePlanetDataStoreEvent { REFRESHED_PLANET = 'REFRESHED_PLANET', REFRESHED_ARTIFACT = 'REFRESHED_ARTIFACT', } interface ReaderDataStoreConfig { contractAddress: EthAddress; viewer: EthAddress | undefined; addressTwitterMap: AddressTwitterMap; contractConstants: ContractConstants; contractsAPI: ContractsAPI; persistentChunkStore: PersistentChunkStore | undefined; } /** * A data store that allows you to retrieve data from the contract, * and combine it with data that is stored in this browser about a * particular user. */ class ReaderDataStore { private readonly viewer: EthAddress | undefined; private readonly addressTwitterMap: AddressTwitterMap; private readonly contractConstants: ContractConstants; private readonly contractsAPI: ContractsAPI; private readonly persistentChunkStore: PersistentChunkStore | undefined; private constructor({ viewer, addressTwitterMap, contractConstants, contractsAPI, persistentChunkStore, }: ReaderDataStoreConfig) { this.viewer = viewer; this.addressTwitterMap = addressTwitterMap; this.contractConstants = contractConstants; this.contractsAPI = contractsAPI; this.persistentChunkStore = persistentChunkStore; } public destroy(): void { this.contractsAPI.destroy(); this.persistentChunkStore?.destroy(); } public static async create({ connection, viewer, contractAddress, }: { connection: EthConnection; viewer: EthAddress | undefined; contractAddress: EthAddress; }): Promise { const contractsAPI = await makeContractsAPI({ connection, contractAddress }); const addressTwitterMap = await getAllTwitters(); const contractConstants = await contractsAPI.getConstants(); const persistentChunkStore = viewer && (await PersistentChunkStore.create({ account: viewer, contractAddress })); const singlePlanetStore = new ReaderDataStore({ contractAddress, viewer, addressTwitterMap, contractConstants, contractsAPI, persistentChunkStore, }); return singlePlanetStore; } public getViewer(): EthAddress | undefined { return this.viewer; } public getTwitter(owner: EthAddress | undefined): string | undefined { if (owner) { return this.addressTwitterMap[owner]; } } private setPlanetLocationIfKnown(planet: Planet): void { let planetLocation = undefined; if (planet && isLocatable(planet)) { // clear the location of the LocatablePlanet, turning it back into a planet /* eslint-disable @typescript-eslint/no-unused-vars */ const { location, biome, ...nonLocatable } = planet; /* eslint-enable @typescript-eslint/no-unused-vars */ planet = nonLocatable; } if (this.persistentChunkStore) { for (const chunk of this.persistentChunkStore.allChunks()) { for (const loc of chunk.planetLocations) { if (loc.hash === planet.locationId) { planetLocation = loc; break; } } if (planetLocation) break; } } if (planetLocation && planet) { (planet as LocatablePlanet).location = planetLocation; (planet as LocatablePlanet).biome = this.getBiome(planetLocation); } } public async loadPlanetFromContract(planetId: LocationId): Promise { const planet = await this.contractsAPI.getPlanetById(planetId); const contractConstants = await this.contractsAPI.getConstants(); if (!planet) { throw new Error(`unable to load planet with id ${planetId}`); } const arrivals = await this.contractsAPI.getArrivalsForPlanet(planetId); arrivals.sort((a, b) => a.arrivalTime - b.arrivalTime); const nowInSeconds = Date.now() / 1000; for (const arrival of arrivals) { if (nowInSeconds < arrival.arrivalTime) break; arrive(planet, [], arrival, undefined, contractConstants); } updatePlanetToTime(planet, [], Date.now(), contractConstants); this.setPlanetLocationIfKnown(planet); return planet; } public async loadArtifactFromContract(artifactId: ArtifactId) { const artifact = await this.contractsAPI.getArtifactById(artifactId); if (!artifact) { throw new Error(`unable to load artifact with id ${artifactId}`); } return artifact; } // copied from GameEntityMemoryStore. needed to determine biome if we know planet location private spaceTypeFromPerlin(perlin: number): SpaceType { if (perlin < this.contractConstants.PERLIN_THRESHOLD_1) { return SpaceType.NEBULA; } else if (perlin < this.contractConstants.PERLIN_THRESHOLD_2) { return SpaceType.SPACE; } else if (perlin < this.contractConstants.PERLIN_THRESHOLD_3) { return SpaceType.DEEP_SPACE; } else { return SpaceType.DEAD_SPACE; } } // copied from GameEntityMemoryStore. needed to determine biome if we know planet location private getBiome(loc: WorldLocation): Biome { const { perlin, biomebase } = loc; const spaceType = this.spaceTypeFromPerlin(perlin); if (spaceType === SpaceType.DEAD_SPACE) return Biome.CORRUPTED; let biome = 3 * spaceType; if (biomebase < this.contractConstants.BIOME_THRESHOLD_1) biome += 1; else if (biomebase < this.contractConstants.BIOME_THRESHOLD_2) biome += 2; else biome += 3; return biome as Biome; } } export default ReaderDataStore; ================================================ FILE: src/Backend/Utils/Animation.ts ================================================ import { DFAnimation, DFStatefulAnimation, PlanetLevel } from '@darkforest_eth/types'; import anime from 'animejs'; export function sinusoidalAnimation(rps: number): DFAnimation { const startTime = Date.now() + Math.random() * 1000; return new DFAnimation(() => { const timeElapsed = Date.now() - startTime; return Math.sin((timeElapsed / 1000) * rps * Math.PI * 2); }); } export function easeInAnimation(durationMs: number, delayMs?: number): DFAnimation { const progress = { percent: 0, }; setTimeout( () => { anime({ targets: progress, // this library will take a value in the `progress` object percent: 1, // .. and animate it between 0 and 1 duration: durationMs, easing: 'easeInOutCubic', // .. using this easing function update: () => {}, }); }, delayMs === undefined ? 0 : delayMs ); return new DFAnimation(() => { return progress.percent; }); } export function emojiEaseOutAnimation(durationMs: number, emoji: string) { const progress = { percent: 0, }; anime({ targets: progress, // this library will take a value in the `progress` object percent: 1, // .. and animate it between 0 and 1 duration: durationMs, easing: 'easeInOutCubic', // .. using this easing function update: () => {}, }); return new DFStatefulAnimation(emoji, () => { return progress.percent; }); } export function constantAnimation(constant: number): DFAnimation { return new DFAnimation(() => constant); } export function planetLevelToAnimationSpeed(level: PlanetLevel): number { return 1 / (1 + level); } ================================================ FILE: src/Backend/Utils/Coordinates.ts ================================================ import { CanvasCoords, WorldCoords } from '@darkforest_eth/types'; export const coordsEqual = (a: WorldCoords, b: WorldCoords): boolean => a.x === b.x && a.y === b.y; export const distL2 = (a: CanvasCoords | WorldCoords, b: CanvasCoords | WorldCoords): number => (a.x - b.x) ** 2 + (a.y - b.y) ** 2; export const vectorLength = (a: CanvasCoords | WorldCoords): number => Math.sqrt(a.x ** 2 + a.y ** 2); export const normalizeVector = (a: WorldCoords): WorldCoords => { const len = vectorLength(a); if (len < 0.00001) return a; // prevent div by 0 return { x: a.x / len, y: a.y / len, }; }; export const scaleVector = (a: WorldCoords, k: number) => { const norm = normalizeVector(a); const len = vectorLength(a); return { x: norm.x * k * len, y: norm.y * k * len, }; }; ================================================ FILE: src/Backend/Utils/SnarkArgsHelper.ts ================================================ import { fakeHash, mimcHash, modPBigInt, modPBigIntNative, perlin } from '@darkforest_eth/hashing'; import { BiomebaseSnarkContractCallArgs, BiomebaseSnarkInput, buildContractCallArgs, fakeProof, InitSnarkContractCallArgs, InitSnarkInput, MoveSnarkContractCallArgs, MoveSnarkInput, RevealSnarkContractCallArgs, RevealSnarkInput, SnarkJSProofAndSignals, } from '@darkforest_eth/snarks'; import biomebaseCircuitPath from '@darkforest_eth/snarks/biomebase.wasm'; import biomebaseZkeyPath from '@darkforest_eth/snarks/biomebase.zkey'; import initCircuitPath from '@darkforest_eth/snarks/init.wasm'; import initZkeyPath from '@darkforest_eth/snarks/init.zkey'; import moveCircuitPath from '@darkforest_eth/snarks/move.wasm'; import moveZkeyPath from '@darkforest_eth/snarks/move.zkey'; import revealCircuitPath from '@darkforest_eth/snarks/reveal.wasm'; import revealZkeyPath from '@darkforest_eth/snarks/reveal.zkey'; import { PerlinConfig } from '@darkforest_eth/types'; import * as bigInt from 'big-integer'; import { BigInteger } from 'big-integer'; import FastQueue from 'fastq'; import { LRUMap } from 'mnemonist'; import { TerminalTextStyle } from '../../Frontend/Utils/TerminalTypes'; import { TerminalHandle } from '../../Frontend/Views/Terminal'; import { HashConfig } from '../../_types/global/GlobalTypes'; type ZKPTask = { taskId: number; input: unknown; circuit: string; // path zkey: string; // path onSuccess: (proof: SnarkJSProofAndSignals) => void; onError: (e: Error) => void; }; type SnarkInput = RevealSnarkInput | InitSnarkInput | MoveSnarkInput | BiomebaseSnarkInput; class SnarkProverQueue { private taskQueue: FastQueue.queue; private taskCount: number; constructor() { this.taskQueue = FastQueue(this.execute.bind(this), 1); this.taskCount = 0; } public doProof( input: SnarkInput, circuit: string, zkey: string ): Promise { const taskId = this.taskCount++; const task = { input, circuit, zkey, taskId, }; return new Promise((resolve, reject) => { this.taskQueue.push(task, (err, result) => { if (err) { reject(err); } else { resolve(result); } }); }); } private async execute( task: ZKPTask, cb: (err: Error | null, result: SnarkJSProofAndSignals | null) => void ) { try { console.log(`proving ${task.taskId}`); const res = await window.snarkjs.groth16.fullProve(task.input, task.circuit, task.zkey); console.log(`proved ${task.taskId}`); cb(null, res); } catch (e) { console.error('error while calculating SNARK proof:'); console.error(e); cb(e, null); } } } class SnarkArgsHelper { /** * How many snark results to keep in an LRU cache. */ private static readonly DEFAULT_SNARK_CACHE_SIZE = 20; private readonly useMockHash: boolean; private readonly snarkProverQueue: SnarkProverQueue; private readonly terminal: React.MutableRefObject; private readonly hashConfig: HashConfig; private readonly spaceTypePerlinOpts: PerlinConfig; private readonly biomebasePerlinOpts: PerlinConfig; private readonly planetHashMimc: (...inputs: number[]) => BigInteger; private moveSnarkCache: LRUMap; private constructor( hashConfig: HashConfig, terminal: React.MutableRefObject, useMockHash: boolean ) { this.useMockHash = useMockHash; this.terminal = terminal; this.snarkProverQueue = new SnarkProverQueue(); this.hashConfig = hashConfig; this.planetHashMimc = useMockHash ? fakeHash(hashConfig.planetRarity) : mimcHash(hashConfig.planetHashKey); this.spaceTypePerlinOpts = { key: hashConfig.spaceTypeKey, scale: hashConfig.perlinLengthScale, mirrorX: hashConfig.perlinMirrorX, mirrorY: hashConfig.perlinMirrorY, floor: true, }; this.biomebasePerlinOpts = { key: hashConfig.biomebaseKey, scale: hashConfig.perlinLengthScale, mirrorX: hashConfig.perlinMirrorX, mirrorY: hashConfig.perlinMirrorY, floor: true, }; this.moveSnarkCache = new LRUMap( SnarkArgsHelper.DEFAULT_SNARK_CACHE_SIZE ); } static create( hashConfig: HashConfig, terminal: React.MutableRefObject, fakeHash = false ): SnarkArgsHelper { const snarkArgsHelper = new SnarkArgsHelper(hashConfig, terminal, fakeHash); return snarkArgsHelper; } setSnarkCacheSize(size: number) { if (size <= 0) { throw new Error(`cache size has to be positive`); } const newCache = new LRUMap(size); const oldKeys = Array.from(this.moveSnarkCache.keys()); for (let i = 0; i < newCache.capacity && i < this.moveSnarkCache.size; i++) { newCache.set(oldKeys[i], this.moveSnarkCache.get(oldKeys[i]) as MoveSnarkContractCallArgs); } this.moveSnarkCache.clear(); this.moveSnarkCache = newCache; } async getRevealArgs(x: number, y: number): Promise { try { const start = Date.now(); this.terminal.current?.println( 'REVEAL: calculating witness and proof', TerminalTextStyle.Sub ); const input: RevealSnarkInput = { x: modPBigInt(x).toString(), y: modPBigInt(y).toString(), PLANETHASH_KEY: this.hashConfig.planetHashKey.toString(), SPACETYPE_KEY: this.hashConfig.spaceTypeKey.toString(), SCALE: this.hashConfig.perlinLengthScale.toString(), xMirror: this.hashConfig.perlinMirrorX ? '1' : '0', yMirror: this.hashConfig.perlinMirrorY ? '1' : '0', }; const { proof, publicSignals }: SnarkJSProofAndSignals = this.useMockHash ? this.fakeRevealProof(x, y) : await this.snarkProverQueue.doProof(input, revealCircuitPath, revealZkeyPath); const ret = buildContractCallArgs(proof, publicSignals) as RevealSnarkContractCallArgs; const end = Date.now(); this.terminal.current?.println( `REVEAL: calculated witness and proof in ${end - start}ms`, TerminalTextStyle.Sub ); return ret; } catch (e) { throw e; } } async getInitArgs(x: number, y: number, r: number): Promise { try { const start = Date.now(); this.terminal.current?.println('INIT: calculating witness and proof', TerminalTextStyle.Sub); const input: InitSnarkInput = { x: modPBigInt(x).toString(), y: modPBigInt(y).toString(), r: r.toString(), PLANETHASH_KEY: this.hashConfig.planetHashKey.toString(), SPACETYPE_KEY: this.hashConfig.spaceTypeKey.toString(), SCALE: this.hashConfig.perlinLengthScale.toString(), xMirror: this.hashConfig.perlinMirrorX ? '1' : '0', yMirror: this.hashConfig.perlinMirrorY ? '1' : '0', }; const { proof, publicSignals }: SnarkJSProofAndSignals = this.useMockHash ? this.fakeInitProof(x, y, r) : await this.snarkProverQueue.doProof(input, initCircuitPath, initZkeyPath); const ret = buildContractCallArgs(proof, publicSignals) as InitSnarkContractCallArgs; const end = Date.now(); this.terminal.current?.println( `INIT: calculated witness and proof in ${end - start}ms`, TerminalTextStyle.Sub ); return ret; } catch (e) { throw e; } } async getMoveArgs( x1: number, y1: number, x2: number, y2: number, r: number, distMax: number ): Promise { const cacheKey = `${x1}-${y1}-${x2}-${y2}-${r}-${distMax}`; const cachedResult = this.moveSnarkCache.get(cacheKey); if (cachedResult) { console.log('MOVE: retrieved snark args from cache'); return Promise.resolve(cachedResult); } try { const start = Date.now(); this.terminal.current?.println('MOVE: calculating witness and proof', TerminalTextStyle.Sub); const input: MoveSnarkInput = { x1: modPBigInt(x1).toString(), y1: modPBigInt(y1).toString(), x2: modPBigInt(x2).toString(), y2: modPBigInt(y2).toString(), r: r.toString(), distMax: distMax.toString(), PLANETHASH_KEY: this.hashConfig.planetHashKey.toString(), SPACETYPE_KEY: this.hashConfig.spaceTypeKey.toString(), SCALE: this.hashConfig.perlinLengthScale.toString(), xMirror: this.hashConfig.perlinMirrorX ? '1' : '0', yMirror: this.hashConfig.perlinMirrorY ? '1' : '0', }; const { proof, publicSignals }: SnarkJSProofAndSignals = this.useMockHash ? this.fakeMoveProof(x1, y1, x2, y2, r, distMax) : await this.snarkProverQueue.doProof(input, moveCircuitPath, moveZkeyPath); const proofArgs = buildContractCallArgs(proof, publicSignals) as MoveSnarkContractCallArgs; const end = Date.now(); this.terminal.current?.println( `MOVE: calculated witness and proof in ${end - start}ms`, TerminalTextStyle.Sub ); this.moveSnarkCache.set(cacheKey, proofArgs); return proofArgs; } catch (e) { throw e; } } async getFindArtifactArgs(x: number, y: number): Promise { try { const start = Date.now(); this.terminal.current?.println( 'ARTIFACT: calculating witness and proof', TerminalTextStyle.Sub ); const input: BiomebaseSnarkInput = { x: modPBigInt(x).toString(), y: modPBigInt(y).toString(), PLANETHASH_KEY: this.hashConfig.planetHashKey.toString(), BIOMEBASE_KEY: this.hashConfig.biomebaseKey.toString(), SCALE: this.hashConfig.perlinLengthScale.toString(), xMirror: this.hashConfig.perlinMirrorX ? '1' : '0', yMirror: this.hashConfig.perlinMirrorY ? '1' : '0', }; const { proof, publicSignals }: SnarkJSProofAndSignals = this.useMockHash ? this.fakeBiomebaseProof(x, y) : await this.snarkProverQueue.doProof(input, biomebaseCircuitPath, biomebaseZkeyPath); const proofArgs = buildContractCallArgs(proof, publicSignals); const end = Date.now(); this.terminal.current?.println( `ARTIFACT: calculated witness and proof in ${end - start}ms`, TerminalTextStyle.Sub ); return proofArgs as BiomebaseSnarkContractCallArgs; } catch (e) { throw e; } } private fakeRevealProof(x: number, y: number) { const hash = this.planetHashMimc(x, y); const perl = perlin({ x, y }, this.spaceTypePerlinOpts); const publicSignals: BigInteger[] = [ hash, bigInt(perl), bigInt(x), bigInt(y), bigInt(this.hashConfig.planetHashKey), bigInt(this.hashConfig.spaceTypeKey), bigInt(this.hashConfig.perlinLengthScale), bigInt(this.hashConfig.perlinMirrorX ? 1 : 0), bigInt(this.hashConfig.perlinMirrorY ? 1 : 0), ]; return fakeProof(publicSignals.map((x) => modPBigIntNative(x).toString(10))); } private fakeInitProof(x: number, y: number, r: number) { const hash = this.planetHashMimc(x, y); const perl = perlin({ x, y }, this.spaceTypePerlinOpts); const publicSignals: BigInteger[] = [ hash, bigInt(perl), bigInt(r), bigInt(this.hashConfig.planetHashKey), bigInt(this.hashConfig.spaceTypeKey), bigInt(this.hashConfig.perlinLengthScale), bigInt(this.hashConfig.perlinMirrorX ? 1 : 0), bigInt(this.hashConfig.perlinMirrorY ? 1 : 0), ]; return fakeProof(publicSignals.map((x) => modPBigIntNative(x).toString(10))); } private fakeMoveProof( x1: number, y1: number, x2: number, y2: number, r: number, distMax: number ) { const hash1 = this.planetHashMimc(x1, y1); const hash2 = this.planetHashMimc(x2, y2); const perl2 = perlin({ x: x2, y: y2 }, this.spaceTypePerlinOpts); const publicSignals: BigInteger[] = [ hash1, hash2, bigInt(perl2), bigInt(r), bigInt(distMax), bigInt(this.hashConfig.planetHashKey), bigInt(this.hashConfig.spaceTypeKey), bigInt(this.hashConfig.perlinLengthScale), bigInt(this.hashConfig.perlinMirrorX ? 1 : 0), bigInt(this.hashConfig.perlinMirrorY ? 1 : 0), ]; return fakeProof(publicSignals.map((x) => modPBigIntNative(x).toString(10))); } private fakeBiomebaseProof(x: number, y: number) { const hash = this.planetHashMimc(x, y); const biomebase = bigInt(perlin({ x, y }, this.biomebasePerlinOpts)); const publicSignals: BigInteger[] = [ hash, biomebase, bigInt(this.hashConfig.planetHashKey), bigInt(this.hashConfig.biomebaseKey), bigInt(this.hashConfig.perlinLengthScale), bigInt(this.hashConfig.perlinMirrorX ? 1 : 0), bigInt(this.hashConfig.perlinMirrorY ? 1 : 0), ]; return fakeProof(publicSignals.map((x) => modPBigIntNative(x).toString(10))); } } export default SnarkArgsHelper; ================================================ FILE: src/Backend/Utils/Utils.ts ================================================ import { formatNumber } from '@darkforest_eth/gamelogic'; import { EthAddress, Planet, SpaceType, Upgrade, UpgradeBranchName } from '@darkforest_eth/types'; import * as bigInt from 'big-integer'; import { BigInteger } from 'big-integer'; import { StatIdx } from '../../_types/global/GlobalTypes'; export const ONE_DAY = 24 * 60 * 60 * 1000; type NestedBigIntArray = (BigInteger | string | NestedBigIntArray)[]; type NestedStringArray = (string | NestedStringArray)[]; export const hexifyBigIntNestedArray = (arr: NestedBigIntArray): NestedStringArray => { return arr.map((value) => { if (Array.isArray(value)) { return hexifyBigIntNestedArray(value); } else { if (typeof value === 'string') { const valueBI = bigInt(value as string); return '0x' + valueBI.toString(16); } else { // eslint-disable-next-line @typescript-eslint/no-explicit-any return '0x' + (value as any).toString(16); } } }); }; /* * returns stat of an upgrade given a stat index */ export const getUpgradeStat = (upgrade: Upgrade, stat: StatIdx): number => { if (stat === StatIdx.EnergyCap) return upgrade.energyCapMultiplier; else if (stat === StatIdx.EnergyGro) return upgrade.energyGroMultiplier; else if (stat === StatIdx.Range) return upgrade.rangeMultiplier; else if (stat === StatIdx.Speed) return upgrade.speedMultiplier; else if (stat === StatIdx.Defense) return upgrade.defMultiplier; else return upgrade.energyCapMultiplier; }; // color utils export const hslStr: (h: number, s: number, l: number) => string = (h, s, l) => { return `hsl(${h % 360},${s}%,${l}%)`; }; function hashToHue(hash: string): number { let seed = bigInt(hash, 16).and(0xffffff).toString(16); seed = '0x' + '0'.repeat(6 - seed.length) + seed; const baseHue = parseInt(seed) % 360; return baseHue; } export const getPlayerColor: (player: EthAddress) => string = (player) => { return hslStr(hashToHue(player.slice(2)), 100, 70); // remove 0x }; export const getOwnerColor: (planet: Planet) => string = (planet) => { return planet.owner ? getPlayerColor(planet.owner) : 'hsl(0,1%,50%)'; }; export const getRandomActionId = () => { const hex = '0123456789abcdef'; let ret = ''; for (let i = 0; i < 10; i += 1) { ret += hex[Math.floor(hex.length * Math.random())]; } return ret; }; export const getFormatProp = (planet: Planet | undefined, prop: string): string => { if (!planet) return '0'; // eslint-disable-next-line @typescript-eslint/no-explicit-any const myPlanet = planet as any; if (prop === 'silverGrowth') return formatNumber(myPlanet[prop] * 60); else return formatNumber(myPlanet[prop]); }; export const getPlanetRank = (planet: Planet | undefined): number => { if (!planet) return 0; return planet.upgradeState.reduce((a, b) => a + b); }; export const getPlanetShortHash = (planet: Planet | undefined): string => { if (!planet) return '00000'; else return planet.locationId.substring(4, 9); }; export const getPlayerShortHash = (address: EthAddress): string => { return address.substring(0, 6); }; export const isFullRank = (planet: Planet | undefined): boolean => { if (!planet) return true; const maxRank = getPlanetMaxRank(planet); const rank = getPlanetRank(planet); return rank >= maxRank; }; export const upgradeName = (branchName: UpgradeBranchName) => { return ['Defense', 'Range', 'Speed'][branchName]; }; export const getPlanetMaxRank = (planet: Planet | undefined): number => { if (!planet) return 0; if (planet.spaceType === SpaceType.NEBULA) return 3; else if (planet.spaceType === SpaceType.SPACE) return 4; else return 5; }; export const titleCase = (title: string): string => title .split(/ /g) .map((word, i) => { // don't capitalize articles unless it's the first word if (i !== 0 && ['of', 'the'].includes(word)) return word; return `${word.substring(0, 1).toUpperCase()}${word.substring(1)}`; }) .join(' '); ================================================ FILE: src/Backend/Utils/WhitelistSnarkArgsHelper.ts ================================================ import { buildContractCallArgs, SnarkJSProofAndSignals, WhitelistSnarkContractCallArgs, WhitelistSnarkInput, } from '@darkforest_eth/snarks'; import whitelistCircuitPath from '@darkforest_eth/snarks/whitelist.wasm'; import whitelistZkeyPath from '@darkforest_eth/snarks/whitelist.zkey'; import { EthAddress } from '@darkforest_eth/types'; import bigInt, { BigInteger } from 'big-integer'; import { TerminalTextStyle } from '../../Frontend/Utils/TerminalTypes'; import { TerminalHandle } from '../../Frontend/Views/Terminal'; /** * Helper method for generating whitelist SNARKS. * This is separate from the existing {@link SnarkArgsHelper} * because whitelist txs require far less setup compared * to SNARKS that are sent in context of the game. */ export const getWhitelistArgs = async ( key: BigInteger, recipient: EthAddress, terminal?: React.MutableRefObject ): Promise => { try { const start = Date.now(); terminal?.current?.println( 'WHITELIST REGISTER: calculating witness and proof', TerminalTextStyle.Sub ); const input: WhitelistSnarkInput = { key: key.toString(), recipient: bigInt(recipient.substring(2), 16).toString(), }; const fullProveResponse = await window.snarkjs.groth16.fullProve( input, whitelistCircuitPath, whitelistZkeyPath ); const { proof, publicSignals }: SnarkJSProofAndSignals = fullProveResponse; const ret = buildContractCallArgs(proof, publicSignals) as WhitelistSnarkContractCallArgs; const end = Date.now(); terminal?.current?.println( `WHITELIST REGISTER: calculated witness and proof in ${end - start}ms`, TerminalTextStyle.Sub ); return ret; } catch (e) { throw e; } }; ================================================ FILE: src/Backend/Utils/Wrapper.ts ================================================ /** * React uses referential identity to detect changes, and rerender. Rather * than copying an object into a new object, to force a rerender, we can * just wrap it in a new {@code Wrapper}, which will force a rerender. */ export class Wrapper { public readonly value: T; public constructor(value: T) { this.value = value; } } ================================================ FILE: src/Frontend/Components/AncientLabel.tsx ================================================ import React from 'react'; /* ancient label */ import styled, { css, keyframes } from 'styled-components'; import { ANCIENT_BLUE, ANCIENT_PURPLE } from '../Styles/Colors'; const shakeAndFlash = keyframes` 0% { transform: skewX(-30deg); color: ${ANCIENT_BLUE}; } 5% { transform: skewX( 30deg); color: ${ANCIENT_BLUE}; text-shadow: -1px -1px 0 ${ANCIENT_BLUE}; } 10% { transform: skewX(-30deg); color: ${ANCIENT_PURPLE}; } 15% { transform: skewX( 30deg); color: ${ANCIENT_PURPLE}; text-shadow: -1px -1px 0 ${ANCIENT_BLUE}; } 20% { transform: skewX( 0deg); color: ${ANCIENT_PURPLE}; } 100% { transform: skewX( 0deg); color: ${ANCIENT_PURPLE}; } `; export const ancientAnim = css` display: inline-block; animation: 1.2s ${shakeAndFlash} linear infinite alternate; `; const StyledAncientLabelAnim = styled.span` ${ancientAnim} color: ${ANCIENT_PURPLE}; `; export const AncientLabelAnim = () => Ancient; const StyledAncientLabel = styled.span` color: ${ANCIENT_PURPLE}; `; export const AncientLabel = () => Ancient; ================================================ FILE: src/Frontend/Components/ArtifactImage.tsx ================================================ import { ArtifactFileColor, artifactFileName, isSpaceShip } from '@darkforest_eth/gamelogic'; import { Artifact } from '@darkforest_eth/types'; import React from 'react'; import styled, { css } from 'styled-components'; import dfstyles from '../Styles/dfstyles'; export const ARTIFACT_URL = 'https://d2wspbczt15cqu.cloudfront.net/v0.6.0-artifacts/'; // const ARTIFACT_URL = '/public/img/artifacts/videos/'; function getArtifactUrl(thumb: boolean, artifact: Artifact, color: ArtifactFileColor): string { const fileName = artifactFileName(true, thumb, artifact, color); return ARTIFACT_URL + fileName; } export function ArtifactImage({ artifact, size, thumb, bgColor, }: { artifact: Artifact; size: number; thumb?: boolean; bgColor?: ArtifactFileColor; }) { const url = getArtifactUrl(thumb || false, artifact, bgColor || ArtifactFileColor.BLUE); const image = isSpaceShip(artifact.artifactType) ? ( ) : ( ); return ( {image} ); } const Container = styled.div` image-rendering: crisp-edges; ${({ width, height }: { width: number; height: number }) => css` width: ${width}px; height: ${height}px; min-width: ${width}px; min-height: ${height}px; background-color: ${dfstyles.colors.artifactBackground}; display: inline-block; `} `; ================================================ FILE: src/Frontend/Components/BiomeAnims.tsx ================================================ import { css, keyframes } from 'styled-components'; const shake = keyframes` 0% { transform: skewX(-30deg); } 5% { transform: skewX(30deg); } 10% { transform: skewX(-30deg); } 15% { transform: skewX(30deg); } 20% { transform: skewX(0deg); } 100% { transform: skewX(0deg); } `; export const shakeAnim = css` display: inline-block; animation: 1.2s ${shake} linear infinite alternate; `; const yellow = '#fed91f'; const orange = '#ff9500'; const red = '#f95e5e'; const base = ` 1px 1px 0px ${red}, `; const burn = keyframes` 0% { text-shadow: ${base} 1px -1px 2px ${yellow}, 0 0px 7px ${red}, 5px -7px 7px ${orange}; } 33% { text-shadow: ${base} 1px -1px 2px ${yellow}, 5px -2px 7px ${red}, 3px 0 10px ${orange}; } 66% { text-shadow: ${base} 1px -1px 2px ${yellow}, 2px -3px 7px ${red}, 0 -3px 10px ${orange}; } 100% { text-shadow: ${base} 1px -1px 2px ${yellow}, 2px -5px 7px ${red}, 5px 0 10px ${orange}; } `; export const burnAnim = css` animation: 5s ${burn} infinite alternate; `; export const wiggle = keyframes` 0% { transform: rotate(-15deg); } 48% { transform: rotate(-15deg); } 50% { transform: rotate(15deg); } 98% { transform: rotate(15deg); } 100% { transform: rotate(-15deg); } `; const s1 = `hsla(198, 78%, 77%, 0.60)`; const s2 = `hsla(198, 78%, 77%, 0.30)`; const s3 = `hsla(198, 78%, 77%, 0.15)`; export const icyAnim = css` text-shadow: 0 -3px 0 ${s1}, 0 -6px 0 ${s2}, 0 -9px 0 ${s3}; display: inline-block; animation: 4s ${wiggle} linear infinite; `; ================================================ FILE: src/Frontend/Components/Btn.tsx ================================================ import { DarkForestButton, DarkForestShortcutButton, ShortcutPressedEvent, } from '@darkforest_eth/ui'; import { createComponent } from '@lit-labs/react'; import React from 'react'; customElements.define(DarkForestButton.tagName, DarkForestButton); customElements.define(DarkForestShortcutButton.tagName, DarkForestShortcutButton); export { DarkForestButton, DarkForestShortcutButton, ShortcutPressedEvent }; // This wraps the customElement in a React wrapper to make it behave exactly like a React component export const Btn = createComponent< DarkForestButton, { onClick: (evt: Event & React.MouseEvent) => void; } >(React, DarkForestButton.tagName, DarkForestButton, { onClick: 'click', }); export const ShortcutBtn = createComponent< DarkForestShortcutButton, { onClick: (evt: Event & React.MouseEvent) => void; onShortcutPressed: (evt: ShortcutPressedEvent) => void; } >(React, DarkForestShortcutButton.tagName, DarkForestShortcutButton, { onClick: 'click', onShortcutPressed: 'shortcut-pressed', }); ================================================ FILE: src/Frontend/Components/Button.tsx ================================================ import React, { useState } from 'react'; interface ButtonProps { onClick?(event: React.MouseEvent): Promise | void; children: React.ReactNode; style?: React.CSSProperties; hoverStyle?: React.CSSProperties; } export default function Button({ children, onClick: _onClick = () => {}, style = {}, hoverStyle = {}, ...rest }: ButtonProps) { const [submitting, setSubmitting] = useState(false); const [hover, setHover] = useState(false); return ( ); } ================================================ FILE: src/Frontend/Components/CapturePlanetButton.tsx ================================================ import { EMPTY_ADDRESS } from '@darkforest_eth/constants'; import { isUnconfirmedCapturePlanetTx, isUnconfirmedInvadePlanetTx } from '@darkforest_eth/serde'; import { Planet, TooltipName } from '@darkforest_eth/types'; import React, { useCallback, useMemo } from 'react'; import styled from 'styled-components'; import { Wrapper } from '../../Backend/Utils/Wrapper'; import { TooltipTrigger } from '../Panes/Tooltip'; import { useAccount, useUIManager } from '../Utils/AppHooks'; import { useEmitterValue } from '../Utils/EmitterHooks'; import { INVADE } from '../Utils/ShortcutConstants'; import { ShortcutBtn } from './Btn'; import { LoadingSpinner } from './LoadingSpinner'; import { MaybeShortcutButton } from './MaybeShortcutButton'; import { Row } from './Row'; import { Green, Red, White } from './Text'; const StyledRow = styled(Row)` .button { margin-bottom: 4px; flex-grow: 1; } `; export function CapturePlanetButton({ planetWrapper, }: { planetWrapper: Wrapper; }) { const uiManager = useUIManager(); const account = useAccount(uiManager); const gameManager = uiManager.getGameManager(); const currentBlockNumber = useEmitterValue(uiManager.getEthConnection().blockNumber$, undefined); const owned = planetWrapper.value?.owner === account; const captureZoneGenerator = uiManager.getCaptureZoneGenerator(); const canGiveScore = useMemo(() => { if (!planetWrapper.value) return; const planet = planetWrapper.value; return uiManager.potentialCaptureScore(planet.planetLevel) > 0; }, [planetWrapper, uiManager]); const shouldShow = useMemo(() => { if (!planetWrapper.value) return false; const planet = planetWrapper.value; return canGiveScore && owned && planet.capturer === EMPTY_ADDRESS; }, [owned, planetWrapper, canGiveScore]); const shouldShowInvade = useMemo(() => { if (!planetWrapper.value) return false; const planet = planetWrapper.value; const inZone = captureZoneGenerator?.isInZone(planet.locationId); return owned && inZone && planet.invader === EMPTY_ADDRESS && planet.capturer === EMPTY_ADDRESS; }, [owned, planetWrapper, captureZoneGenerator]); const planetHasEnoughEnergy = useMemo(() => { if (!planetWrapper.value) return false; const { energy, energyCap } = planetWrapper.value; return energy > energyCap * 0.8; // real percentage is 78. need time for contract to catch up. }, [planetWrapper]); const invadable = useMemo(() => { if (!planetWrapper.value) return false; const planet = planetWrapper.value; return planet.capturer === EMPTY_ADDRESS && planet.invader === EMPTY_ADDRESS; }, [planetWrapper]); const invading = useMemo( () => planetWrapper.value?.transactions?.hasTransaction(isUnconfirmedInvadePlanetTx), [planetWrapper] ); const invade = useCallback(() => { if (!planetWrapper.value) return; gameManager.invadePlanet(planetWrapper.value.locationId); }, [gameManager, planetWrapper]); const shouldShowCapture = useMemo(() => { if (!planetWrapper.value) return; const planet = planetWrapper.value; return owned && planet.capturer === EMPTY_ADDRESS && planet.invader !== EMPTY_ADDRESS; }, [owned, planetWrapper]); const blocksLeft = useMemo(() => { if (!planetWrapper.value || !currentBlockNumber) { return undefined; } const planet = planetWrapper.value; if (!planet.invadeStartBlock) return undefined; const holdBlocksRequired = uiManager .getGameManager() .getContractConstants().CAPTURE_ZONE_HOLD_BLOCKS_REQUIRED; // it is possible that we have an outdated currentBlockNumber in this calculation // this would result in an incorrect number of blocks required being shown // only show a max of holdBlocksRequired instead return Math.min( planet.invadeStartBlock + holdBlocksRequired - currentBlockNumber, holdBlocksRequired ); }, [uiManager, planetWrapper, currentBlockNumber]); const capturable = useMemo(() => { return blocksLeft !== undefined && blocksLeft <= 0 && planetHasEnoughEnergy; }, [planetHasEnoughEnergy, blocksLeft]); const capturing = useMemo( () => planetWrapper.value?.transactions?.hasTransaction(isUnconfirmedCapturePlanetTx), [planetWrapper] ); const capture = useCallback(() => { if (!planetWrapper.value) return; gameManager.capturePlanet(planetWrapper.value.locationId); }, [gameManager, planetWrapper]); return ( {shouldShow && ( <> {shouldShowInvade && ( Invade this planet. } > {invading ? : 'Invade'} )} {shouldShowCapture && ( Capture this planet for score!{' '} {!!blocksLeft && blocksLeft >= 0 && ( <> You must wait {blocksLeft} blocks until you can capture this planet. )} {!planetHasEnoughEnergy && ( The planet requires above 80% energy before you can capture it. )} } > {capturing ? : 'Capture!'} )} )} ); } ================================================ FILE: src/Frontend/Components/CoreUI.tsx ================================================ import colors from 'color'; import React, { ChangeEvent, useCallback } from 'react'; import styled, { css } from 'styled-components'; import dfstyles from '../Styles/dfstyles'; import { DFZIndex } from '../Utils/constants'; export const InlineBlock = styled.div` display: inline-block; `; export const Separator = styled.div` width: 100%; box-sizing: border-box; padding-left: 2px; padding-right: 2px; height: 1px; background-color: ${dfstyles.colors.borderDark}; `; export const FloatRight = styled.div` float: right; `; export const TextButton = styled.span` color: ${dfstyles.colors.subtext}; cursor: pointer; user-select: none; text-decoration: underline; &:hover { color: white; } `; export const Padded = styled.div` ${({ left, top, right, bottom, }: { left?: string; top?: string; right?: string; bottom?: string; }) => css` padding-left: ${left || '8px'}; padding-top: ${top || '8px'}; padding-right: ${right || '8px'}; padding-bottom: ${bottom || '8px'}; `} `; export const BorderlessPane = styled.div` transition: 200ms; display: flex; flex-direction: column; z-index: ${DFZIndex.MenuBar}; margin: 8px; padding: 8px; border-radius: ${dfstyles.borderRadius}; background-color: ${dfstyles.colors.background}; border-radius: ${dfstyles.borderRadius}; background-color: ${dfstyles.colors.background}; `; export const Underline = styled.span` text-decoration: underline; `; export const Display = styled.div` ${({ visible }: { visible?: boolean }) => css` ${!visible && `display: none;`} `} `; export const Emphasized = styled.span` font-weight: bold; color: ${dfstyles.colors.subtext}; `; export const HeaderText = styled.div` color: ${dfstyles.colors.text}; text-decoration: underline; font-weight: bold; display: inline; `; export const SectionHeader = styled(HeaderText)` margin-bottom: 16px; display: block; `; export const Section = styled.div` padding: 1em 0; &:first-child { margin-top: -8px; } &:last-child { border-bottom: none; } `; export const Bottom = styled.div` position: absolute; bottom: 0; left: 0; `; export const VerticalSplit = function ({ children, }: { children: [React.ReactNode, React.ReactNode]; }) { return ( {children[0]} {children[1]} ); }; const VerticalSplitRow = styled.div` display: flex; flex-direction: row; gap: 8px; `; const VerticalSplitChild = styled.div` flex: 1 1 50%; display: flex; flex-direction: column; gap: 8px; `; export const FullHeight = styled.div` height: 100%; `; /** * Fills parent width, aligns children horizontally in the center. */ export const AlignCenterHorizontally = styled.div` display: inline-flex; flex-direction: row; justify-content: center; align-items: center; `; export const AlignCenterVertically = styled.div` height: 100%; display: flex; flex-direction: column; justify-content: center; align-items: center; `; /** * Expands to fill space in a flexbox. */ export const Expand = styled.div` display: inline-box; flex-grow: 1; `; /** * Don't shrink in a flexbox. */ export const DontShrink = styled.div` display: inline-box; flex-shrink: 0; `; /** * This is the link that all core ui in Dark Forest should use. Please! */ export function Link( props: { to?: string; color?: string; openInThisTab?: boolean; children: React.ReactNode; } & React.HtmlHTMLAttributes ) { const { to, color, openInThisTab, children } = props; return ( {children} ); } const LinkImpl = styled.a` cursor: pointer; ${({ color }: { color?: string }) => css` text-decoration: underline; color: ${color || dfstyles.colors.dfblue}; } &:hover { color: ${colors(color || dfstyles.colors.dfblue) .lighten(0.3) .hex()}; } `} `; /** * Inline block rectangle, measured in ems, default 1em by 1em. */ export const EmSpacer = styled.div` ${({ width, height }: { width?: number; height?: number }) => css` width: ${width === undefined ? '1em' : width}; height: ${height === undefined ? '1em' : height}; flex-grow: 0; flex-shrink: 0; ${width && !height ? 'display: inline-block;' : ''} ${width ? `width: ${width}em;` : ''} ${height ? `height: ${height}em;min-height:${height}em;` : ''} `} `; export const Spacer = styled.div` ${({ width, height }: { width?: number; height?: number }) => css` width: 1px; height: 1px; ${width && !height ? 'display: inline-block;' : ''} ${width ? `width: ${width}px;` : ''} ${height ? `height: ${height}px;min-height:${height}px;` : ''} `} `; export const Truncate = styled.div` ${({ maxWidth }: { maxWidth?: string }) => css` vertical-align: bottom; display: inline-block; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; ${maxWidth !== undefined && `max-width: ${maxWidth};`}; `} `; export const Title = styled(Truncate)` flex-grow: 1; text-align: left; `; /** * The container element into which a plugin renders its html elements. * Contains styles for child elements so that plugins can use UI * that is consistent with the rest of Dark Forest's UI. Keeping this up * to date will be an ongoing challange, but there's probably some better * way to do this. */ export const PluginElements = styled.div` color: ${dfstyles.colors.text}; width: 400px; min-height: 100px; max-height: 600px; overflow: scroll; button { border: 1px solid white; border-radius: 4px; padding: 0 0.3em; transition: background-color 0.2s colors 0.2s; &:hover { background-color: ${dfstyles.colors.text}; color: black; border: 1px transparent; } } `; export const MaxWidth = styled.div` ${({ width }: { width: string }) => { return css` max-width: ${width}; `; }} `; export const Hidden = styled.div` display: none; `; export const Select = styled.select` ${({ wide }: { wide?: boolean }) => css` outline: none; background: ${dfstyles.colors.background}; color: ${dfstyles.colors.subtext}; border-radius: 4px; border: 1px solid ${dfstyles.colors.border}; width: ${wide ? '100%' : '12em'}; padding: 2px 6px; cursor: pointer; &:hover { border: 1px solid ${dfstyles.colors.subtext}; background: ${dfstyles.colors.subtext}; color: ${dfstyles.colors.background}; } `} `; /** * Controllable input that allows the user to select from one of the * given string values. */ export function SelectFrom({ values, value, setValue, labels, style, wide, }: { values: string[]; value: string; setValue: (value: string) => void; labels: string[]; style?: React.CSSProperties; wide?: boolean; }) { const onChange = useCallback( (e: ChangeEvent) => { setValue(e.target.value); }, [setValue] ); const copyOfValues = [...values]; const copyOfLabels = [...labels]; if (!copyOfValues.includes(value)) { copyOfLabels.push(value); copyOfValues.push(value); } return ( ); } export const CenterRow = styled.div` display: flex; justify-content: flex-start; align-items: center; flex-direction: row; `; /** * A box which centers some darkened text. Useful for displaying * *somthing* instead of empty space, if there isn't something to * be displayed. Think of it as a placeholder. */ export const CenterBackgroundSubtext = styled.div` ${({ width, height }: { width: string; height: string }) => css` display: flex; justify-content: center; align-items: center; flex-direction: column; color: ${dfstyles.colors.subtext}; width: ${width}; height: ${height}; user-select: none; text-align: center; `} `; /** * Expands to fit the width of container. Is itself a flex box that spreads out its children * horizontally. */ export const SpreadApart = styled.div` width: 100%; box-sizing: border-box; flex-grow: 1; display: inline-flex; justify-content: space-between; flex-direction: row; align-items: center; &:first-child { border-left: none; } `; /** * Expands to fit the width of container. Is itself a flex box that spreads out its children * horizontally. */ export const Spread = styled.div` width: 100%; box-sizing: border-box; flex-grow: 1; display: inline-flex; justify-content: space-around; flex-direction: row; align-items: center; &:first-child { border-left: none; } `; ================================================ FILE: src/Frontend/Components/Corner.tsx ================================================ import React from 'react'; interface CornerProps { children: React.ReactNode; top?: boolean; left?: boolean; right?: boolean; bottom?: boolean; style?: React.CSSProperties; } const cornerStyle: React.CSSProperties = { position: 'absolute', margin: '4px', }; export function Corner({ children, style = {}, top = false, bottom = false, left = false, right = false, }: CornerProps) { const posStyles: React.CSSProperties = { top: top ? 0 : undefined, bottom: bottom ? 0 : undefined, left: left ? 0 : undefined, right: right ? 0 : undefined, }; return (
{children}
); } ================================================ FILE: src/Frontend/Components/DisplayGasPrices.tsx ================================================ import { GasPrices } from '@darkforest_eth/types'; import React from 'react'; export function DisplayGasPrices({ gasPrices }: { gasPrices?: GasPrices }) { return (
{!gasPrices ? ( 'unknown' ) : ( <> slo: {gasPrices.slow + ' '} avg: {gasPrices.average + ' '} fst: {gasPrices.fast + ' '} )}
); } ================================================ FILE: src/Frontend/Components/Email.tsx ================================================ import React, { useState } from 'react'; import { EmailResponse, submitInterestedEmail, submitUnsubscribeEmail, } from '../../Backend/Network/UtilityServerAPI'; import dfstyles from '../Styles/dfstyles'; import Button from './Button'; import { Green, Red, Sub } from './Text'; export const enum EmailCTAMode { SUBSCRIBE, UNSUBSCRIBE, } const styles: { [name: string]: React.CSSProperties; } = { wrapper: { display: 'flex', flexDirection: 'column', alignItems: 'center', }, hwrapper: { display: 'flex', flexDirection: 'row', alignItems: 'center', }, btn: { background: 'rgba(0, 0, 0, 0)', color: dfstyles.colors.text, marginLeft: '8pt', width: '24pt', height: '24pt', borderRadius: '12pt', lineHeight: '24pt', transition: 'background 0.2s, color 0.2s', }, btnHov: { color: dfstyles.colors.background, background: dfstyles.colors.text, }, input: { padding: '4px 8px', borderRadius: '5px', border: `1px solid ${dfstyles.colors.text}`, transition: 'color 0.2s, background 0.2s, width 0.2s', }, }; export const EmailCTA = ({ mode }: { mode: EmailCTAMode }) => { const [email, setEmail] = useState(''); const [status, setStatus] = useState(null); const [focus, setFocus] = useState(false); const doSubmit = async () => { const response = mode === EmailCTAMode.SUBSCRIBE ? await submitInterestedEmail(email) : await submitUnsubscribeEmail(email); setStatus(response); if (response === EmailResponse.Success) { setEmail(''); } }; const responseToMessage = (response: EmailResponse): React.ReactNode => { if (response === EmailResponse.Success) { if (mode === EmailCTAMode.SUBSCRIBE) { return ( email successfully recorded ); } else { return ( successfully unsubscribed ); } } else if (response === EmailResponse.Invalid) return 'invalid address'; else if (response === EmailResponse.ServerError) return 'server error'; else { console.error('invalid email outcome'); return ( Error ); } }; return (

{mode === EmailCTAMode.SUBSCRIBE ? 'info' : 'unsubscribe'}:

setEmail(e.target.value)} placeholder={'name@email.com'} onKeyDown={(e) => { if (e.keyCode === 13) e.preventDefault(); }} onKeyUp={(e) => { e.preventDefault(); if (e.keyCode === 13) doSubmit(); }} onFocus={() => setFocus(true)} onBlur={() => setFocus(false)} />
{status !== null &&

{responseToMessage(status)}

}
); }; ================================================ FILE: src/Frontend/Components/GameLandingPageComponents.tsx ================================================ import React, { Dispatch, SetStateAction, useLayoutEffect } from 'react'; import styled, { css } from 'styled-components'; import dfstyles from '../Styles/dfstyles'; import UIEmitter, { UIEmitterEvent } from '../Utils/UIEmitter'; export const enum InitRenderState { NONE, LOADING, COMPLETE, } type LandingWrapperProps = { children: React.ReactNode; initRender: InitRenderState; terminalEnabled: boolean; }; const StyledWrapper = styled.div<{ initRender: InitRenderState; }>` width: 100%; height: 100%; margin: 0; padding: 0; display: flex; flex-direction: row; justify-content: ${(props) => props.initRender !== InitRenderState.NONE ? 'space-between' : 'space-around'}; `; export function Wrapper({ children, initRender }: LandingWrapperProps) { return {children}; } const STWInit = css` position: absolute; width: ${dfstyles.game.terminalWidth}; right: 0; top: 0; padding: 1em; font-size: ${dfstyles.game.terminalFontSize}; `; const STWNoInit = css` max-width: 60em; width: 60%; padding: 2em 0; font-size: ${dfstyles.fontSizeS}; `; const StyledTerminalWrapper = styled.div<{ initRender: InitRenderState; terminalEnabled: boolean; }>` display: ${({ initRender, terminalEnabled }) => { if (initRender === InitRenderState.NONE) return 'block'; else return terminalEnabled ? 'block' : 'none'; }}; border-left: ${({ terminalEnabled, initRender }) => terminalEnabled && initRender !== InitRenderState.NONE ? `1px solid ${dfstyles.colors.border}` : 'none'}; height: 100%; // overflow: hidden; background: ${dfstyles.colors.background}; position: relative; ${(props) => (props.initRender !== InitRenderState.NONE ? STWInit : STWNoInit)}; @media (max-width: 660px) { width: 100%; padding: 1.5em 2em; } `; export function TerminalWrapper({ children, initRender, terminalEnabled }: LandingWrapperProps) { return ( {children} ); } const StyledTerminalToggler = styled.div<{ terminalEnabled: boolean }>` position: absolute; right: 0; top: 0; height: 100%; width: 1em; background: ${dfstyles.colors.text}; z-index: 1000; color: ${dfstyles.colors.background}; display: flex; flex-direction: column; justify-content: space-around; align-items: center; opacity: 0; &:hover { opacity: 1; cursor: pointer; } & span { font-size: 1.25em; transform: scaleY(2); } `; export function TerminalToggler({ terminalEnabled, setTerminalEnabled, }: { terminalEnabled: boolean; setTerminalEnabled: Dispatch>; }) { const uiEmitter = UIEmitter.getInstance(); useLayoutEffect(() => { uiEmitter.emit(UIEmitterEvent.UIChange); }, [terminalEnabled, uiEmitter]); return ( setTerminalEnabled((b: boolean): boolean => !b)} > {terminalEnabled ? '>' : '<'} ); } const StyledGameWindowWrapper = styled.div<{ initRender: InitRenderState; terminalEnabled: boolean; }>` background: ${dfstyles.colors.background}; position: absolute; left: 0; top: 0; width: ${(props) => props.terminalEnabled ? `calc(100% - ${dfstyles.game.terminalWidth})` : '100%'}; height: 100%; display: ${(props) => (props.initRender !== InitRenderState.NONE ? 'block' : 'none')}; `; export function GameWindowWrapper({ children, initRender, terminalEnabled }: LandingWrapperProps) { return ( {initRender && <>{children}} ); } export const Hidden = styled.div` display: none; position: absolute; top: -10000; left: -10000; `; ================================================ FILE: src/Frontend/Components/GameWindowComponents.tsx ================================================ import React from 'react'; import styled from 'styled-components'; import dfstyles from '../../Frontend/Styles/dfstyles'; export const WindowWrapper = styled.div` overflow: hidden; display: flex; flex-direction: column; background: ${dfstyles.colors.background}; height: 100%; width: 100%; font-size: ${dfstyles.game.fontSize}; `; export const StyledPane = styled.div` display: flex; flex-direction: column; .pane-header { padding: 16pt 8pt; display: flex; flex-direction: row; justify-content: space-between; align-items: center; height: 1.5em; & > p { text-decoration: underline; line-height: 1em; } & > div { display: flex; flex-direction: row; justify-content: flex-end; & > p, & > span, & > div { margin-left: 0.2em; } } } .pane-content { padding: 4pt 8pt 8pt 8pt; position: relative; } `; export type PaneProps = { title: string | ((small: boolean) => React.ReactNode); children: React.ReactNode; headerItems?: React.ReactNode; }; export const MainWindow = styled.div` // position and sizing height: 100%; width: 100%; position: absolute; top: 0; flex-grow: 1; // styling background: ${dfstyles.colors.background}; // display inner things display: flex; flex-direction: row; justify-content: space-between; `; export const CanvasContainer = styled.div` flex-grow: 1; position: relative; `; export const CanvasWrapper = styled.div` position: absolute; width: 100%; height: 100%; `; export const UpperLeft = styled.div` position: absolute; left: 0; display: flex; flex-direction: row; justify-content: flex-start; `; ================================================ FILE: src/Frontend/Components/Icons.tsx ================================================ // should be able to be treated as a text element import { Planet, UpgradeBranchName } from '@darkforest_eth/types'; import { DarkForestIcon, IconType } from '@darkforest_eth/ui'; import { createComponent } from '@lit-labs/react'; import React from 'react'; import { getPlanetRank, isFullRank } from '../../Backend/Utils/Utils'; import { StatIdx } from '../../_types/global/GlobalTypes'; // TODO: Decide if this is the best place to register the custom elements customElements.define(DarkForestIcon.tagName, DarkForestIcon); // This wraps the customElement in a React wrapper to make it behave exactly like a React component export const Icon = createComponent(React, DarkForestIcon.tagName, DarkForestIcon, { // If we had any, we would map DOM events to React handlers passed in as props. For example: // onClick: 'click' }); // Re-export the IconType abstract type & the "enum" object for easier access export { IconType } from '@darkforest_eth/ui'; // Utilities for calculating an Icon from some context. // I think these should be made into utilities that return the `IconType` // instead of an Icon JSXElement export const RankIcon = ({ planet }: { planet: Planet | undefined }) => { const rank = getPlanetRank(planet); if (isFullRank(planet)) return ; if (rank === 1) return ; else if (rank === 2) return ; else if (rank === 3) return ; return ; }; export const BranchIcon = ({ branch }: { branch: number }) => { if (branch === UpgradeBranchName.Range) return ; // TODO: Wat else if (branch === UpgradeBranchName.Defense) return ; else return ; }; export const StatIcon = ({ stat }: { stat: StatIdx }) => { if (stat === StatIdx.Defense) return ; else if (stat === StatIdx.EnergyGro) return ; else if (stat === StatIdx.EnergyCap) return ; else if (stat === StatIdx.Range) return ; else if (stat === StatIdx.Speed) return ; // TODO: lulz what else return ; }; /** Allow for tweaking the size of an icon based on the context. Biome & Spacetype Notifications should fill the notification box Others should be 3/4's the size and centered */ interface AlertIcon { height?: string; width?: string; } export const Quasar = ({ height, width }: AlertIcon) => { return ; }; export const FoundRuins = ({ height, width }: AlertIcon) => { return ; }; export const FoundSpacetimeRip = ({ height, width }: AlertIcon) => { return ( ); }; export const FoundSilver = ({ height, width }: AlertIcon) => { return ; }; export const FoundTradingPost = ({ height, width }: AlertIcon) => { return ( ); }; export const FoundSpace = ({ height, width }: AlertIcon) => { return ; }; export const FoundDeepSpace = ({ height, width }: AlertIcon) => { return ; }; export const FoundDeadSpace = ({ height, width }: AlertIcon) => { return ; }; export const FoundPirates = ({ height, width }: AlertIcon) => { return ; }; export const Generic = ({ height, width }: AlertIcon) => { return ; }; export const ArtifactFound = ({ height, width }: AlertIcon) => { return ; }; export const ArtifactProspected = ({ height, width }: AlertIcon) => { return ; }; export const FoundOcean = ({ height, width }: AlertIcon) => { return ; }; export const FoundForest = ({ height, width }: AlertIcon) => { return ; }; export const FoundGrassland = ({ height, width }: AlertIcon) => { return ; }; export const FoundTundra = ({ height, width }: AlertIcon) => { return ; }; export const FoundSwamp = ({ height, width }: AlertIcon) => { return ; }; export const FoundDesert = ({ height, width }: AlertIcon) => { return ; }; export const FoundIce = ({ height, width }: AlertIcon) => { return ; }; export const FoundWasteland = ({ height, width }: AlertIcon) => { return ; }; export const FoundLava = ({ height, width }: AlertIcon) => { return ; }; export const FoundCorrupted = ({ height, width }: AlertIcon) => { return ; }; export const PlanetAttacked = ({ height, width }: AlertIcon) => { return ; }; export const PlanetLost = ({ height, width }: AlertIcon) => { return ; }; export const PlanetConquered = ({ height, width }: AlertIcon) => { return ; }; export const MetPlayer = ({ height, width }: AlertIcon) => { return ; }; export const TxAccepted = ({ height, width }: AlertIcon) => { return ; }; export const TxConfirmed = ({ height, width }: AlertIcon) => { return ( ); }; export const TxInitialized = ({ height, width }: AlertIcon) => { return ( ); }; export const TxDeclined = ({ height, width }: AlertIcon) => { return ; }; ================================================ FILE: src/Frontend/Components/Input.tsx ================================================ import { DarkForestCheckbox, DarkForestColorInput, DarkForestNumberInput, DarkForestTextInput, } from '@darkforest_eth/ui'; import { createComponent } from '@lit-labs/react'; import React from 'react'; customElements.define(DarkForestCheckbox.tagName, DarkForestCheckbox); customElements.define(DarkForestColorInput.tagName, DarkForestColorInput); customElements.define(DarkForestNumberInput.tagName, DarkForestNumberInput); customElements.define(DarkForestTextInput.tagName, DarkForestTextInput); export { DarkForestCheckbox, DarkForestColorInput, DarkForestNumberInput, DarkForestTextInput }; // This wraps the customElement in a React wrapper to make it behave exactly like a React component export const Checkbox = createComponent< DarkForestCheckbox, { onChange: (e: Event & React.ChangeEvent) => void; } >(React, DarkForestCheckbox.tagName, DarkForestCheckbox, { onChange: 'input', }); // This wraps the customElement in a React wrapper to make it behave exactly like a React component export const ColorInput = createComponent< DarkForestColorInput, { onChange: (e: Event & React.ChangeEvent) => void; } >(React, DarkForestColorInput.tagName, DarkForestColorInput, { // The `input` event is more like what we expect as `onChange` in React onChange: 'input', }); // This wraps the customElement in a React wrapper to make it behave exactly like a React component export const NumberInput = createComponent< DarkForestNumberInput, { onChange: (e: Event & React.ChangeEvent) => void; } >(React, DarkForestNumberInput.tagName, DarkForestNumberInput, { // The `input` event is more like what we expect as `onChange` in React onChange: 'input', }); // This wraps the customElement in a React wrapper to make it behave exactly like a React component export const TextInput = createComponent< DarkForestTextInput, { onChange: (e: Event & React.ChangeEvent) => void; onBlur: (e: Event) => void; } >(React, DarkForestTextInput.tagName, DarkForestTextInput, { // The `input` event is more like what we expect as `onChange` in React onChange: 'input', onBlur: 'blur', }); ================================================ FILE: src/Frontend/Components/Labels/ArtifactLabels.tsx ================================================ import { isAncient } from '@darkforest_eth/gamelogic'; import { Artifact, ArtifactRarity, ArtifactRarityNames, ArtifactTypeNames, BiomeNames, } from '@darkforest_eth/types'; import React from 'react'; import styled from 'styled-components'; import { RarityColors } from '../../Styles/Colors'; import { LegendaryLabel } from './LegendaryLabel'; import { MythicLabel } from './MythicLabel'; export const ArtifactRarityText = ({ rarity }: { rarity: ArtifactRarity }) => ( <>{ArtifactRarityNames[rarity]} ); export const ArtifactBiomeText = ({ artifact }: { artifact: Artifact }) => ( <>{isAncient(artifact) ? 'Ancient' : BiomeNames[artifact.planetBiome]} ); export const ArtifactTypeText = ({ artifact }: { artifact: Artifact }) => ( <>{ArtifactTypeNames[artifact.artifactType]} ); // colored labels export const StyledArtifactRarityLabel = styled.span<{ rarity: ArtifactRarity }>` color: ${({ rarity }) => RarityColors[rarity]}; `; export const ArtifactRarityLabel = ({ rarity }: { rarity: ArtifactRarity }) => ( ); export const ArtifactRarityLabelAnim = ({ rarity }: { rarity: ArtifactRarity }) => rarity === ArtifactRarity.Mythic ? ( ) : rarity === ArtifactRarity.Legendary ? ( ) : ( ); // combined labels export const ArtifactRarityBiomeTypeText = ({ artifact }: { artifact: Artifact }) => ( <> {' '} ); ================================================ FILE: src/Frontend/Components/Labels/BiomeLabels.tsx ================================================ import { isAncient, isLocatable } from '@darkforest_eth/gamelogic'; import { Artifact, Biome, BiomeNames, LocatablePlanet, Planet } from '@darkforest_eth/types'; import React from 'react'; import styled from 'styled-components'; import { BiomeTextColors } from '../../Styles/Colors'; import { AncientLabel, AncientLabelAnim } from '../AncientLabel'; import { icyAnim, shakeAnim } from '../BiomeAnims'; import { LavaLabel } from './LavaLabel'; import { WastelandLabel } from './WastelandLabel'; /** Renders colored text corresponding to a biome */ export const BiomeLabel = styled.span<{ biome: Biome }>` color: ${({ biome }) => BiomeTextColors[biome]}; `; const StyledBiomeLabelAnim = styled(BiomeLabel)<{ biome: Biome }>` ${({ biome }) => biome === Biome.CORRUPTED && shakeAnim}; ${({ biome }) => biome === Biome.ICE && icyAnim}; `; /** Renders animated colored text corresponding to a biome */ export const BiomeLabelAnim = ({ biome }: { biome: Biome }) => biome === Biome.LAVA ? ( ) : biome === Biome.WASTELAND ? ( ) : ( {BiomeNames[biome]} ); export const PlanetBiomeLabelAnim = ({ planet }: { planet: LocatablePlanet }) => ( ); export const OptionalPlanetBiomeLabelAnim = ({ planet }: { planet: Planet | undefined }) => ( <>{planet && isLocatable(planet) && } ); export const ArtifactBiomeLabel = ({ artifact }: { artifact: Artifact }) => isAncient(artifact) ? : ; export const ArtifactBiomeLabelAnim = ({ artifact }: { artifact: Artifact }) => isAncient(artifact) ? : ; ================================================ FILE: src/Frontend/Components/Labels/KeywordLabels.tsx ================================================ import { TooltipName } from '@darkforest_eth/types'; import React from 'react'; import styled from 'styled-components'; import { TooltipTrigger } from '../../Panes/Tooltip'; import dfstyles from '../../Styles/dfstyles'; const StyledSilverLabel = styled.span` color: ${dfstyles.colors.text}; `; export const SilverLabel = () => Silver; export const SilverLabelTip = () => ( ); const StyledScoreLabel = styled.span` color: ${dfstyles.colors.dfyellow}; -webkit-text-stroke: 1px; `; export const ScoreLabel = () => Score; export const ScoreLabelTip = () => ( ); ================================================ FILE: src/Frontend/Components/Labels/Labels.tsx ================================================ import { EMPTY_ADDRESS } from '@darkforest_eth/constants'; import { getPlayerColor } from '@darkforest_eth/procedural'; import { EthAddress } from '@darkforest_eth/types'; import colorFn from 'color'; import React from 'react'; import { usePlayer, useUIManager } from '../../Utils/AppHooks'; import { Link } from '../CoreUI'; import { Sub } from '../Text'; import { TextPreview } from '../TextPreview'; export function AccountLabel({ includeAddressIfHasTwitter, ethAddress, width, style, }: { includeAddressIfHasTwitter?: boolean; ethAddress?: EthAddress; width?: string; style?: React.CSSProperties; }) { const uiManager = useUIManager(); const player = usePlayer(uiManager, ethAddress); if (player.value !== undefined && player.value.twitter !== undefined) { const color = colorFn(getPlayerColor(player.value.address)).darken(0.5).hex(); return ( {includeAddressIfHasTwitter && ( {' '} '} unFocusedWidth={'50px'} focusedWidth={'50px'} /> )} ); } if (ethAddress === EMPTY_ADDRESS) { return <>nobody; } return ( '} unFocusedWidth={width ?? '150px'} focusedWidth={width ?? '150px'} /> ); } /** * Link to a twitter account. */ export function TwitterLink({ twitter, color }: { twitter: string; color?: string }) { return ( @{twitter} ); } ================================================ FILE: src/Frontend/Components/Labels/LavaLabel.tsx ================================================ import { Biome, BiomeNames } from '@darkforest_eth/types'; import React from 'react'; import styled, { keyframes } from 'styled-components'; import { BiomeTextColors } from '../../Styles/Colors'; const Wrap = styled.span` position: relative; top: -3px; `; const Static = styled.span` opacity: 0; `; const bounce = keyframes` 0% { top: 0px; filter: none; } 20% { top: -3px; filter: brightness(1.75); } 40% { top: -3px; filter: brightness(1.75); } 60% { top: 0px; filter: none; } 100% { top: 0px; filter: none;} `; const AnimDelay = styled.span<{ i: number }>` position: relative; animation: ${bounce} 1s ease-in-out infinite alternate; ${({ i }) => `animation-delay: ${-i * 0.1}s;`} `; const Anim = styled.span` position: absolute; color: ${BiomeTextColors[Biome.LAVA]}; transition: color 0.2s; display: inline-flex; flex-direction: row; top: 0; left: 0; `; // TODO pull this logic out from here and SpaceTimeRipLabel into a component export function LavaLabelRaw() { return ( {BiomeNames[Biome.LAVA]} {BiomeNames[Biome.LAVA].split('').map((c, i) => ( {c === ' ' ? <>  : c} ))} ); } export const LavaLabel = React.memo(LavaLabelRaw); ================================================ FILE: src/Frontend/Components/Labels/LegendaryLabel.tsx ================================================ import { ArtifactRarity, ArtifactRarityNames } from '@darkforest_eth/types'; import React from 'react'; import styled, { keyframes } from 'styled-components'; import { RarityColors } from '../../Styles/Colors'; const color = keyframes` 0% { color: ${RarityColors[ArtifactRarity.Legendary]}; } 70% { color: ${RarityColors[ArtifactRarity.Legendary]}; } 100% { color: #ffffff; } `; const AnimDelay = styled.span<{ i: number }>` animation: ${color} 1s linear infinite alternate; ${({ i }) => `animation-delay: ${-i * 0.04}s;`} `; const Anim = styled.span` color: ${RarityColors[ArtifactRarity.Legendary]}; `; function LegendaryLabelRaw() { return ( {ArtifactRarityNames[ArtifactRarity.Legendary].split('').map((c, i) => ( {c === ' ' ? <>  : c} ))} ); } export const LegendaryLabel = React.memo(LegendaryLabelRaw); ================================================ FILE: src/Frontend/Components/Labels/MythicLabel.tsx ================================================ import { ArtifactRarity, ArtifactRarityNames } from '@darkforest_eth/types'; import React from 'react'; import styled, { keyframes } from 'styled-components'; import { RarityColors } from '../../Styles/Colors'; const color = keyframes` 0% { filter: hue-rotate(0); } 30% { filter: hue-rotate(0); } 100% { filter: hue-rotate(360deg); } `; const AnimDelay = styled.span<{ i: number }>` animation: ${color} 1s ease-in-out infinite alternate; ${({ i }) => `animation-delay: ${-i * 0.04}s;`} `; const Anim = styled.span` color: ${RarityColors[ArtifactRarity.Mythic]}; `; export function MythicLabelText({ text, style }: { text: string; style?: React.CSSProperties }) { return ( {text.split('').map((c, i) => ( {c === ' ' ? <>  : c} ))} ); } function MythicLabelRaw() { return ; } export const MythicLabel = React.memo(MythicLabelRaw); ================================================ FILE: src/Frontend/Components/Labels/PlanetLabels.tsx ================================================ import { EMPTY_ADDRESS } from '@darkforest_eth/constants'; import { formatNumber } from '@darkforest_eth/gamelogic'; import { getPlayerColor } from '@darkforest_eth/procedural'; import { Planet, PlanetType, PlanetTypeNames } from '@darkforest_eth/types'; import React from 'react'; import { getPlanetRank } from '../../../Backend/Utils/Utils'; import dfstyles from '../../Styles/dfstyles'; import { useAccount, usePlayer, useUIManager } from '../../Utils/AppHooks'; import { EmSpacer } from '../CoreUI'; import { Colored, Sub, Subber, White } from '../Text'; import { TextPreview } from '../TextPreview'; import { OptionalPlanetBiomeLabelAnim } from './BiomeLabels'; import { TwitterLink } from './Labels'; import { SpacetimeRipLabel } from './SpacetimeRipLabel'; /* note that we generally prefer `Planet | undefined` over `Planet` because it makes it easier to pass in selected / hovering planet from the emitters */ /* stat stuff */ export function StatText({ planet, getStat, style, buff, }: { planet: Planet | undefined; getStat: (p: Planet) => number; style?: React.CSSProperties; buff?: number; }) { const textStyle = { ...style, color: buff ? dfstyles.colors.dforange : undefined, }; let content; if (planet) { let stat = getStat(planet); if (buff) { stat *= buff; } content = formatNumber(stat, 2); } else { content = 'n/a'; } return {content}; } export function GrowthText({ planet, getStat, style, }: { planet: Planet | undefined; getStat: (p: Planet) => number; style?: React.CSSProperties; }) { const paused = planet && planet.pausers > 0; const statStyle = { ...style, textDecoration: paused ? 'line-through' : 'none' }; return ; } const getSilver = (p: Planet) => p.silver; export const SilverText = ({ planet }: { planet: Planet | undefined }) => ( ); const getSilverCap = (p: Planet) => p.silverCap; export const SilverCapText = ({ planet }: { planet: Planet | undefined }) => ( ); const getEnergy = (p: Planet) => p.energy; export const EnergyText = ({ planet }: { planet: Planet | undefined }) => ( ); const getEnergyCap = (p: Planet) => p.energyCap; export const EnergyCapText = ({ planet }: { planet: Planet | undefined }) => ( ); export function PlanetEnergyLabel({ planet }: { planet: Planet | undefined }) { return ( / ); } export function PlanetSilverLabel({ planet }: { planet: Planet | undefined }) { return ( / ); } const getDefense = (p: Planet) => p.defense; export const DefenseText = ({ planet }: { planet: Planet | undefined }) => ( ); const getRange = (p: Planet) => p.range; export const RangeText = ({ planet, buff }: { planet: Planet | undefined; buff?: number }) => ( ); const getJunk = (p: Planet) => p.spaceJunk; export const JunkText = ({ planet }: { planet: Planet | undefined }) => ( ); const getSpeed = (p: Planet) => p.speed; export const SpeedText = ({ planet, buff }: { planet: Planet | undefined; buff?: number }) => ( ); const getEnergyGrowth = (p: Planet) => p.energyGrowth; export const EnergyGrowthText = ({ planet }: { planet: Planet | undefined }) => ( ); const getSilverGrowth = (p: Planet) => p.silverGrowth; export const SilverGrowthText = ({ planet }: { planet: Planet | undefined }) => ( ); // level and rank stuff export const PlanetLevelText = ({ planet }: { planet: Planet | undefined }) => planet ? <>Level {planet.planetLevel} : <>; export const PlanetRankText = ({ planet }: { planet: Planet | undefined }) => planet ? <>Rank {getPlanetRank(planet)} : <>; export const LevelRankText = ({ planet, delim, }: { planet: Planet | undefined; delim?: string; }) => ( <> {delim || ', '} ); export const LevelRankTextEm = ({ planet, delim, }: { planet: Planet | undefined; delim?: string; }) => planet ? ( lvl {planet.planetLevel} {delim || ', '} rank {getPlanetRank(planet)} ) : ( <> ); export const PlanetTypeLabelAnim = ({ planet }: { planet: Planet | undefined }) => ( <> {planet && (planet.planetType === PlanetType.TRADING_POST ? ( ) : ( PlanetTypeNames[planet.planetType] ))} ); export const PlanetBiomeTypeLabelAnim = ({ planet }: { planet: Planet | undefined }) => ( <> {planet?.planetType !== PlanetType.TRADING_POST && ( <> )} ); export const PlanetLevel = ({ planet }: { planet: Planet | undefined }) => { if (!planet) return <>; return ( <> {'Level '} {planet.planetLevel} ); }; export const PlanetRank = ({ planet }: { planet: Planet | undefined }) => { if (!planet) return <>; return ( <> {'Rank '} {getPlanetRank(planet)} ); }; /** * Either 'yours' in green text, */ export function PlanetOwnerLabel({ planet, abbreviateOwnAddress, colorWithOwnerColor, }: { planet: Planet | undefined; abbreviateOwnAddress?: boolean; colorWithOwnerColor?: boolean; }) { const uiManager = useUIManager(); const account = useAccount(uiManager); const owner = usePlayer(uiManager, planet?.owner); const defaultColor = dfstyles.colors.subtext; if (!planet) return <>/; if (planet.owner === EMPTY_ADDRESS) return Unclaimed; if (abbreviateOwnAddress && planet.owner === account) { return yours!; } const color = colorWithOwnerColor ? defaultColor : getPlayerColor(planet.owner); if (planet.owner && owner.value?.twitter) { return ; } return ( ); } ================================================ FILE: src/Frontend/Components/Labels/SpacetimeRipLabel.tsx ================================================ import { PlanetType, PlanetTypeNames } from '@darkforest_eth/types'; import React from 'react'; import styled, { keyframes } from 'styled-components'; const Wrap = styled.span` position: relative; top: -5px; `; const Static = styled.span` opacity: 0; `; const bounce = keyframes` from { top: 3px; filter: hue-rotate(0); } to { top: -3px; filter: hue-rotate(360deg); } `; const AnimDelay = styled.span<{ i: number }>` position: relative; animation: ${bounce} 0.5s ease-in-out infinite alternate; ${({ i }) => `animation-delay: ${-i * 0.04}s;`} `; const Anim = styled.span` position: absolute; color: hsl(0, 100%, 75%); transition: color 0.2s; display: inline-flex; flex-direction: row; top: 0; left: 0; `; function SpacetimeRipLabelRaw() { return ( {PlanetTypeNames[PlanetType.TRADING_POST]} {PlanetTypeNames[PlanetType.TRADING_POST].split('').map((c, i) => ( {c === ' ' ? <>  : c} ))} ); } export const SpacetimeRipLabel = React.memo(SpacetimeRipLabelRaw); ================================================ FILE: src/Frontend/Components/Labels/WastelandLabel.tsx ================================================ import { Biome, BiomeNames } from '@darkforest_eth/types'; import React from 'react'; import styled from 'styled-components'; import { BiomeTextColors } from '../../Styles/Colors'; import { burnAnim, wiggle } from '../BiomeAnims'; const Wrap = styled.span` position: relative; top: -3px; `; const Static = styled.span` opacity: 0; `; const AnimDelay = styled.span<{ i: number }>` position: relative; animation: ${wiggle} ${({ i }) => 9.4 + ((i * 0.7) % 1.0)}s linear infinite; ${({ i }) => `animation-delay: ${-i * 5.9}s;`} color: ${BiomeTextColors[Biome.WASTELAND]}; `; const Anim = styled.span` position: absolute; ${burnAnim}; transition: color 0.2s; display: inline-flex; flex-direction: row; top: 0; left: 0; `; // TODO pull this logic out from here and SpaceTimeRipLabel into a component export function WastelandLabelRaw() { return ( {BiomeNames[Biome.WASTELAND]} {BiomeNames[Biome.WASTELAND].split('').map((c, i) => ( {c === ' ' ? <>  : c} ))} ); } export const WastelandLabel = React.memo(WastelandLabelRaw); ================================================ FILE: src/Frontend/Components/LoadingSpinner.tsx ================================================ import React, { useEffect, useState } from 'react'; export function LoadingSpinner({ initialText, rate }: { initialText?: string; rate?: number }) { const speed = rate || 100; const text = initialText || 'Loading...'; const [currentText, setCurrentText] = useState(text); useEffect(() => { let cursor = 0; const interval = setInterval(() => { cursor = (cursor + 1) % text.length; setCurrentText(text.substr(cursor) + text.substr(0, cursor)); }, speed); return () => clearInterval(interval); }, [text, speed]); return {currentText}; } ================================================ FILE: src/Frontend/Components/MaybeShortcutButton.tsx ================================================ import { Setting } from '@darkforest_eth/types'; import React from 'react'; import { useUIManager } from '../Utils/AppHooks'; import { useBooleanSetting } from '../Utils/SettingsHooks'; import { Btn, ShortcutBtn } from './Btn'; /** * A button that will show shortcuts if enabled globally in the game, otherwise it will display a normal button * * Must ONLY be used when a GameUIManager is available. */ export function MaybeShortcutButton( props: React.ComponentProps | React.ComponentProps ) { const uiManager = useUIManager(); const [disableDefaultShortcuts] = useBooleanSetting(uiManager, Setting.DisableDefaultShortcuts); if (disableDefaultShortcuts) { return ; } else { return ; } } ================================================ FILE: src/Frontend/Components/MineArtifactButton.tsx ================================================ import { isUnconfirmedFindArtifactTx, isUnconfirmedProspectPlanetTx } from '@darkforest_eth/serde'; import { ArtifactType, Planet, PlanetType, TooltipName } from '@darkforest_eth/types'; import React, { useCallback, useMemo } from 'react'; import styled from 'styled-components'; import { isFindable } from '../../Backend/GameLogic/ArrivalUtils'; import { Wrapper } from '../../Backend/Utils/Wrapper'; import { TooltipTrigger } from '../Panes/Tooltip'; import { useAccount, useUIManager } from '../Utils/AppHooks'; import { useEmitterValue } from '../Utils/EmitterHooks'; import { MINE_ARTIFACT } from '../Utils/ShortcutConstants'; import { ShortcutBtn } from './Btn'; import { MaybeShortcutButton } from './MaybeShortcutButton'; import { Row } from './Row'; import { Red } from './Text'; const StyledArtifactRow = styled(Row)` .button { margin-top: 4px; margin-bottom: 4px; flex-grow: 1; } `; export function MineArtifactButton({ planetWrapper, }: { planetWrapper: Wrapper; }) { const uiManager = useUIManager(); const account = useAccount(uiManager); const gameManager = uiManager.getGameManager(); const currentBlockNumber = useEmitterValue(uiManager.getEthConnection().blockNumber$, undefined); const owned = planetWrapper.value?.owner === account; const isRuins = useMemo( () => planetWrapper.value?.planetType === PlanetType.RUINS, [planetWrapper] ); const isDestroyed = useMemo(() => planetWrapper.value?.destroyed, [planetWrapper]); const hasGear = useMemo( () => planetWrapper.value?.heldArtifactIds .map((id) => uiManager.getArtifactWithId(id)) .find((artifact) => artifact?.artifactType === ArtifactType.ShipGear), [planetWrapper, uiManager] ); const prospectable = isRuins && hasGear; const prospecting = useMemo( () => planetWrapper.value?.transactions?.hasTransaction(isUnconfirmedProspectPlanetTx), [planetWrapper] ); const findable = planetWrapper.value && isFindable(planetWrapper.value, currentBlockNumber); const find = useCallback(() => { if (!planetWrapper.value) return; gameManager.findArtifact(planetWrapper.value.locationId); }, [gameManager, planetWrapper]); const finding = useMemo( () => planetWrapper.value?.transactions?.hasTransaction(isUnconfirmedFindArtifactTx), [planetWrapper] ); const mine = useCallback(async () => { if (!planetWrapper.value) return; const tx = await gameManager.prospectPlanet(planetWrapper.value.locationId); await tx.confirmedPromise; await gameManager.findArtifact(planetWrapper.value.locationId); }, [gameManager, planetWrapper]); const alreadyMined = planetWrapper.value?.hasTriedFindingArtifact; return ( {owned && !alreadyMined && !isDestroyed && ( <> {isRuins && !findable && ( You must have a Gear ship on this planet to prospect artifacts. ) } > {!prospecting ? 'Prospect Artifact' : 'Prospecting...'} )} {isRuins && findable && ( You must have a Gear ship on this planet to find artifacts. You must find an artifact within 256 blocks of prospecting. ) } > {!finding ? 'Find Artifact' : 'Finding Artifact...'} )} )} ); } ================================================ FILE: src/Frontend/Components/Modal.tsx ================================================ import { DarkForestModal, PositionChangedEvent } from '@darkforest_eth/ui'; import { createComponent } from '@lit-labs/react'; import React from 'react'; customElements.define(DarkForestModal.tagName, DarkForestModal); export { DarkForestModal, PositionChangedEvent }; // This wraps the customElement in a React wrapper to make it behave exactly like a React component export const Modal = createComponent< DarkForestModal, { onMouseDown: (evt: Event & React.MouseEvent) => void; onPositionChanged: (evt: PositionChangedEvent) => void; } >(React, DarkForestModal.tagName, DarkForestModal, { onMouseDown: 'mousedown', onPositionChanged: 'position-changed', }); ================================================ FILE: src/Frontend/Components/OpenPaneButtons.tsx ================================================ import { LocationId } from '@darkforest_eth/types'; import React, { useCallback } from 'react'; import { BroadcastPane, BroadcastPaneHelpContent } from '../Panes/BroadcastPane'; import { HatPane } from '../Panes/HatPane'; import { ManagePlanetArtifactsHelpContent, ManagePlanetArtifactsPane, PlanetInfoHelpContent, } from '../Panes/ManagePlanetArtifacts/ManagePlanetArtifactsPane'; import { PlanetInfoPane } from '../Panes/PlanetInfoPane'; import { UpgradeDetailsPane, UpgradeDetailsPaneHelpContent } from '../Panes/UpgradeDetailsPane'; import { TOGGLE_BROADCAST_PANE, TOGGLE_HAT_PANE, TOGGLE_PLANET_ARTIFACTS_PANE, TOGGLE_PLANET_INFO_PANE, TOGGLE_UPGRADES_PANE, } from '../Utils/ShortcutConstants'; import { ModalHandle } from '../Views/ModalPane'; import { MaybeShortcutButton } from './MaybeShortcutButton'; export function OpenPaneButton({ modal, title, element, helpContent, shortcut, }: { modal: ModalHandle; title: string; element: () => React.ReactElement; helpContent?: React.ReactElement; shortcut?: string; }) { const open = useCallback(() => { modal.push({ title, element, helpContent, }); }, [title, element, helpContent, modal]); return ( {title} ); } export function OpenHatPaneButton({ modal, planetId, }: { modal: ModalHandle; planetId: LocationId | undefined; }) { return ( } /> ); } export function OpenBroadcastPaneButton({ modal, planetId, }: { modal: ModalHandle; planetId: LocationId | undefined; }) { return ( } helpContent={BroadcastPaneHelpContent()} /> ); } export function OpenUpgradeDetailsPaneButton({ modal, planetId, }: { modal: ModalHandle; planetId: LocationId | undefined; }) { return ( } helpContent={UpgradeDetailsPaneHelpContent()} /> ); } export function OpenManagePlanetArtifactsButton({ modal, planetId, }: { modal: ModalHandle; planetId: LocationId | undefined; }) { return ( } helpContent={ManagePlanetArtifactsHelpContent()} /> ); } export function OpenPlanetInfoButton({ modal, planetId, }: { modal: ModalHandle; planetId: LocationId | undefined; }) { return ( } helpContent={PlanetInfoHelpContent()} /> ); } ================================================ FILE: src/Frontend/Components/PluginModal.tsx ================================================ import { ModalId } from '@darkforest_eth/types'; import React, { useCallback, useState } from 'react'; import ReactDOM from 'react-dom'; import { useUIManager } from '../Utils/AppHooks'; import { useEmitterSubscribe } from '../Utils/EmitterHooks'; import { ModalPane } from '../Views/ModalPane'; import { PluginElements } from './CoreUI'; export function PluginModal({ title, container, id, width, onClose, onRender, }: { title: string; id: ModalId; container: Element; width?: string; onClose: () => void; onRender: (el: HTMLDivElement) => void; }) { const uiManager = useUIManager(); const modalManager = uiManager.getModalManager(); /** * We use the existence of a window position for a given modal as an indicator * that it should be opened on page load. This is to satisfy the feature of * peristent modal positions across browser sessions for a given account. */ const isModalOpen = (modalId: ModalId) => { const pos = modalManager.getModalPosition(modalId); if (pos) { return pos.state !== 'closed'; } else { return false; } }; const [visible, setVisible] = useState(isModalOpen(id)); useEmitterSubscribe( modalManager.modalPositionChanged$, (modalId) => { if (modalId === id) { setVisible(isModalOpen(id)); } }, [setVisible, isModalOpen, id] ); const handleRef = useCallback( (el: HTMLDivElement | null) => { if (el !== null) { onRender(el); } }, [onRender] ); return ReactDOM.createPortal( , container ); } ================================================ FILE: src/Frontend/Components/ReadMore.tsx ================================================ import React, { useCallback, useState } from 'react'; import styled from 'styled-components'; import { EmSpacer, TextButton } from './CoreUI'; export function ReadMore({ children, height, text, toggleButtonMargin, }: { children: React.ReactNode[] | React.ReactNode; height?: string; text?: string; toggleButtonMargin?: string; }) { const [collapsed, setCollapsed] = useState(true); const toggle = useCallback(() => { setCollapsed((collapsed) => !collapsed); }, []); return ( <> {children} {!collapsed && } {text ?? (collapsed ? 'more' : 'less')} info ); } const ContentContainer = styled.div` margin-top: 4px; overflow: hidden; text-align: justify; line-height: 1.2em; `; ================================================ FILE: src/Frontend/Components/RemoteModal.tsx ================================================ import { ModalId } from '@darkforest_eth/types'; import * as React from 'react'; import ReactDOM from 'react-dom'; import { ModalPane } from '../Views/ModalPane'; /** * Allows you to instantiate a modal, and render it into the desired element. * Useful for loading temporary modals from ANYWHERE in the UI, not just * {@link GameWindowLayout} */ export function RemoteModal({ title, container, children, visible, onClose, id, width, }: React.PropsWithChildren<{ title: string; id: ModalId; container: Element; visible: boolean; onClose: () => void; width?: string; }>) { return ReactDOM.createPortal( {children} , container ); } ================================================ FILE: src/Frontend/Components/Row.tsx ================================================ import { DarkForestRow } from '@darkforest_eth/ui'; import { createComponent } from '@lit-labs/react'; import React from 'react'; customElements.define(DarkForestRow.tagName, DarkForestRow); // This wraps the customElement in a React wrapper to make it behave exactly like a React component export const Row = createComponent(React, DarkForestRow.tagName, DarkForestRow); ================================================ FILE: src/Frontend/Components/Slider.tsx ================================================ import { DarkForestSlider, DarkForestSliderHandle } from '@darkforest_eth/ui'; import { createComponent } from '@lit-labs/react'; import React from 'react'; customElements.define(DarkForestSlider.tagName, DarkForestSlider); customElements.define(DarkForestSliderHandle.tagName, DarkForestSliderHandle); export { DarkForestSlider, DarkForestSliderHandle }; // This wraps the customElement in a React wrapper to make it behave exactly like a React component export const Slider = createComponent< DarkForestSlider, { onChange: (e: Event & React.ChangeEvent) => void; } >(React, DarkForestSlider.tagName, DarkForestSlider, { // The `input` event is more like what we expect as `onChange` in React (live-updating as you slide) onChange: 'input', }); // This wraps the customElement in a React wrapper to make it behave exactly like a React component export const SliderHandle = createComponent< DarkForestSliderHandle, { onChange: (e: Event & React.ChangeEvent) => void; } >(React, DarkForestSliderHandle.tagName, DarkForestSliderHandle, { // The `change` event on a handle is all we really care about on handles onChange: 'change', }); ================================================ FILE: src/Frontend/Components/Text.tsx ================================================ import { BLOCK_EXPLORER_URL } from '@darkforest_eth/constants'; import { isLocatable } from '@darkforest_eth/gamelogic'; import { artifactName, getPlanetName } from '@darkforest_eth/procedural'; import { Artifact, ArtifactId, Chunk, Planet, Transaction, WorldCoords, } from '@darkforest_eth/types'; import React, { useEffect, useState } from 'react'; import styled from 'styled-components'; import Viewport from '../Game/Viewport'; import dfstyles from '../Styles/dfstyles'; import { useUIManager } from '../Utils/AppHooks'; import UIEmitter, { UIEmitterEvent } from '../Utils/UIEmitter'; import { Link } from './CoreUI'; export function BlinkCursor() { const [visible, setVisible] = useState(false); useEffect(() => { const id = setInterval(() => { setVisible((v) => { return !v; }); }, 500); return () => clearInterval(id); }, []); return {visible ? '|' : ''} ; } export const Green = styled.span` color: ${dfstyles.colors.dfgreen}; `; export const Sub = styled.span` color: ${dfstyles.colors.subtext}; `; export const Subber = styled.span` color: ${dfstyles.colors.subbertext}; `; export const Text = styled.span` color: ${dfstyles.colors.text}; `; export const White = styled.span` color: ${dfstyles.colors.dfwhite}; `; export const Red = styled.span` color: ${dfstyles.colors.dfred}; `; export const Gold = styled.span` color: ${dfstyles.colors.dfyellow}; `; export const Colored = styled.span<{ color: string }>` color: ${({ color }) => color}; `; export const Blue = styled.span` color: ${dfstyles.colors.dfblue}; `; export const Invisible = styled.span` color: rgba(0, 0, 0, 0); `; export const Smaller = styled.span` font-size: 80%; `; export const HideSmall = styled.span` @media (max-width: ${dfstyles.screenSizeS}) { display: none; } `; export function TxLink({ tx }: { tx: Transaction }) { if (tx.hash) { return ( <> window.open(`${BLOCK_EXPLORER_URL}/${tx.hash}`)}> {tx.hash.substring(0, 7)} ); } return -; } export function CenterPlanetLink({ planet, children, }: { planet: Planet; children: React.ReactNode; }) { const uiManager = useUIManager(); return ( { if (isLocatable(planet)) { uiManager.centerPlanet(planet); } }} > {children} ); } export function ArtifactNameLink({ id }: { id: ArtifactId }) { const uiManager = useUIManager(); const artifact: Artifact | undefined = uiManager && uiManager.getArtifactWithId(id); const click = () => { UIEmitter.getInstance().emit(UIEmitterEvent.ShowArtifact, artifact); }; return {artifactName(artifact)}; } export function PlanetNameLink({ planet }: { planet: Planet }) { return {getPlanetName(planet)}; } export function CenterChunkLink({ chunk, children }: { chunk: Chunk; children: React.ReactNode }) { return Viewport.getInstance().centerChunk(chunk)}>{children}; } export function FAQ04Link({ children }: { children: React.ReactNode }) { return {children} ; } export const LongDash = () => ( - ); export const Coords = ({ coords: { x, y } }: { coords: WorldCoords }) => ( ({x}, {y}) ); ================================================ FILE: src/Frontend/Components/TextLoadingBar.tsx ================================================ import React, { useImperativeHandle, useState } from 'react'; import styled from 'styled-components'; import dfstyles from '../Styles/dfstyles'; import { Sub } from './Text'; export interface LoadingBarHandle { setFractionCompleted: (fractionCompleted: number) => void; } interface LoadingBarProps { prettyEntityName: string; } export const TextLoadingBar = React.forwardRef( TextLoadingBarImpl ); export function TextLoadingBarImpl( { prettyEntityName }: LoadingBarProps, ref: React.Ref ) { // value between 0 and 1 const [fractionCompleted, setFractionCompleted] = useState(0); useImperativeHandle(ref, () => ({ setFractionCompleted })); const progressWidth = 20; let progressText = ''; for (let i = 0; i < progressWidth; i++) { if (i < Math.floor(progressWidth * fractionCompleted)) { progressText += '='; } else { progressText += '\u00a0'; //   } } const percentText = Math.floor(fractionCompleted * 100) .toString() .padStart(3, ' '); return ( [{progressText}]{' '} {percentText}%{' '} {prettyEntityName} ); } const LoadingTitle = styled.div` display: inline-block; color: ${dfstyles.colors.text}; `; ================================================ FILE: src/Frontend/Components/TextPreview.tsx ================================================ import React, { useCallback, useState } from 'react'; import styled, { css } from 'styled-components'; import { TextInput } from './Input'; const DEFAULT_UNFOCUSED_WIDTH = '50px'; const DEFAULT_FOCUSED_WIDTH = '150px'; export function TextPreview({ text = '', unFocusedWidth = DEFAULT_UNFOCUSED_WIDTH, focusedWidth = DEFAULT_FOCUSED_WIDTH, style, }: { text?: string; unFocusedWidth?: string; focusedWidth?: string; maxLength?: number; style?: React.CSSProperties; }) { const [isTextBox, setIsTextbox] = useState(false); const onClick = useCallback((e: React.SyntheticEvent) => { e.stopPropagation(); setIsTextbox(true); }, []); const onBlur = useCallback(() => { setIsTextbox(false); }, []); if (isTextBox) { return ( ); } return ( {text} ); } const ShortenedText = styled.span` ${({ width }: { width: string }) => css` cursor: zoom-in; display: inline-block; width: ${width}; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; vertical-align: bottom; `} `; const InputContainer = styled.div` ${({ width }: { width: string }) => css` display: inline-block; width: ${width}; `} `; ================================================ FILE: src/Frontend/Components/Theme.tsx ================================================ import { DarkForestTheme } from '@darkforest_eth/ui'; import { createComponent } from '@lit-labs/react'; import React from 'react'; customElements.define(DarkForestTheme.tagName, DarkForestTheme); // This wraps the customElement in a React wrapper to make it behave exactly like a React component export const Theme = createComponent(React, DarkForestTheme.tagName, DarkForestTheme, { // If we had any, we would map DOM events to React handlers passed in as props. For example: // onClick: 'click' }); ================================================ FILE: src/Frontend/Components/TimeUntil.tsx ================================================ import React, { useEffect, useState } from 'react'; /** * Given a timestamp, displays the amount of time until the timestamp from now in hh:mm:ss format. * If the timestamp is in the past, displays the given hardcoded value. */ export function TimeUntil({ timestamp, ifPassed }: { timestamp: number; ifPassed: string }) { const [value, setValue] = useState(''); useEffect(() => { const update = () => { const msWait = timestamp - Date.now(); if (msWait <= 0) { setValue(ifPassed); } else { setValue(formatDuration(msWait)); } }; const interval = setInterval(() => { update(); }, 1000); update(); return () => clearInterval(interval); }, [timestamp, ifPassed]); return {value}; } export function formatDuration(msDuration: number): string { const hoursWait = Math.floor(msDuration / 1000 / 60 / 60); const minutes = Math.floor((msDuration - hoursWait * 60 * 60 * 1000) / 1000 / 60); const seconds = Math.floor( (msDuration - hoursWait * 60 * 60 * 1000 - minutes * 60 * 1000) / 1000 ); const str = hoursWait + ':' + (minutes + '').padStart(2, '0') + ':' + (seconds + '').padStart(2, '0'); return str; } ================================================ FILE: src/Frontend/EntryPoints/index.tsx ================================================ import * as React from 'react'; import * as ReactDOM from 'react-dom'; import App from '../Pages/App'; import '../Styles/font/stylesheet.css'; import '../Styles/icomoon/style.css'; import '../Styles/preflight.css'; import '../Styles/style.css'; ReactDOM.render(, document.getElementById('root')); ================================================ FILE: src/Frontend/Game/ControllableCanvas.tsx ================================================ import { Renderer } from '@darkforest_eth/renderer'; import { CursorState, ModalManagerEvent, Setting } from '@darkforest_eth/types'; import React, { useCallback, useEffect, useLayoutEffect, useRef, useState } from 'react'; import styled from 'styled-components'; import { useUIManager } from '../Utils/AppHooks'; import UIEmitter, { UIEmitterEvent } from '../Utils/UIEmitter'; import Viewport from './Viewport'; const CanvasWrapper = styled.div` width: 100%; height: 100%; position: relative; canvas { width: 100%; height: 100%; position: absolute; &#buffer { width: auto; height: auto; display: none; } } // TODO put this into a global style canvas, img { image-rendering: -moz-crisp-edges; image-rendering: -webkit-crisp-edges; image-rendering: pixelated; image-rendering: crisp-edges; } `; export default function ControllableCanvas() { // html canvas element width and height. viewport dimensions are tracked by viewport obj const [width, setWidth] = useState(window.innerWidth); const [height, setHeight] = useState(window.innerHeight); const canvasRef = useRef(null); const glRef = useRef(null); const bufferRef = useRef(null); const evtRef = canvasRef; const gameUIManager = useUIManager(); const modalManager = gameUIManager.getModalManager(); const [targeting, setTargeting] = useState(false); useEffect(() => { const updateTargeting = (newstate: CursorState) => { setTargeting(newstate === CursorState.TargetingExplorer); }; modalManager.on(ModalManagerEvent.StateChanged, updateTargeting); return () => { modalManager.removeListener(ModalManagerEvent.StateChanged, updateTargeting); }; }, [modalManager]); const doResize = useCallback(() => { const uiEmitter: UIEmitter = UIEmitter.getInstance(); if (canvasRef.current) { setWidth(canvasRef.current.clientWidth); setHeight(canvasRef.current.clientHeight); uiEmitter.emit(UIEmitterEvent.WindowResize); } }, [canvasRef]); // TODO fix this useLayoutEffect(() => { if (canvasRef.current) doResize(); }, [ // dep array gives eslint issues, but it's fine i tested it i swear - Alan canvasRef, doResize, /* eslint-disable react-hooks/exhaustive-deps */ canvasRef.current?.offsetWidth, canvasRef.current?.offsetHeight, /* eslint-enable react-hooks/exhaustive-deps */ ]); useEffect(() => { if (!gameUIManager) return; const uiEmitter: UIEmitter = UIEmitter.getInstance(); function onResize() { doResize(); uiEmitter.emit(UIEmitterEvent.WindowResize); } const onWheel = (e: WheelEvent): void => { e.preventDefault(); const { deltaY } = e; uiEmitter.emit(UIEmitterEvent.CanvasScroll, deltaY); }; const canvas = evtRef.current; if (!canvas || !canvasRef.current || !glRef.current || !bufferRef.current) return; // This zooms your home world in really close to show the awesome details // TODO: Store this as it changes and re-initialize to that if stored const defaultWorldUnits = 4; Viewport.initialize(gameUIManager, defaultWorldUnits, canvas); Renderer.initialize( canvasRef.current, glRef.current, bufferRef.current, Viewport.getInstance(), gameUIManager, { spaceColors: { innerNebulaColor: gameUIManager.getStringSetting(Setting.RendererColorInnerNebula), nebulaColor: gameUIManager.getStringSetting(Setting.RendererColorNebula), spaceColor: gameUIManager.getStringSetting(Setting.RendererColorSpace), deepSpaceColor: gameUIManager.getStringSetting(Setting.RendererColorDeepSpace), deadSpaceColor: gameUIManager.getStringSetting(Setting.RendererColorDeadSpace), }, } ); // We can't attach the wheel event onto the canvas due to: // https://www.chromestatus.com/features/6662647093133312 canvas.addEventListener('wheel', onWheel); window.addEventListener('resize', onResize); uiEmitter.on(UIEmitterEvent.UIChange, doResize); return () => { Viewport.destroyInstance(); Renderer.destroy(); canvas.removeEventListener('wheel', onWheel); window.removeEventListener('resize', onResize); uiEmitter.removeListener(UIEmitterEvent.UIChange, doResize); }; }, [gameUIManager, doResize, canvasRef, glRef, bufferRef, evtRef]); // attach event listeners useEffect(() => { if (!evtRef.current) return; const canvas = evtRef.current; const uiEmitter: UIEmitter = UIEmitter.getInstance(); function onMouseEvent(emitEventName: UIEmitterEvent, mouseEvent: MouseEvent) { const rect = canvas.getBoundingClientRect(); const canvasX = mouseEvent.clientX - rect.left; const canvasY = mouseEvent.clientY - rect.top; uiEmitter.emit(emitEventName, { x: canvasX, y: canvasY }); } const onMouseDown = (e: MouseEvent) => { onMouseEvent(UIEmitterEvent.CanvasMouseDown, e); }; // this is the root of the mousemove event const onMouseMove = (e: MouseEvent) => { onMouseEvent(UIEmitterEvent.CanvasMouseMove, e); }; const onMouseUp = (e: MouseEvent) => { onMouseEvent(UIEmitterEvent.CanvasMouseUp, e); }; // TODO convert this to mouseleave const onMouseOut = () => { uiEmitter.emit(UIEmitterEvent.CanvasMouseOut); }; canvas.addEventListener('mousedown', onMouseDown); canvas.addEventListener('mousemove', onMouseMove); canvas.addEventListener('mouseup', onMouseUp); canvas.addEventListener('mouseout', onMouseOut); return () => { canvas.removeEventListener('mousedown', onMouseDown); canvas.removeEventListener('mousemove', onMouseMove); canvas.removeEventListener('mouseup', onMouseUp); canvas.removeEventListener('mouseout', onMouseOut); }; }, [evtRef]); return ( ); } ================================================ FILE: src/Frontend/Game/ModalManager.ts ================================================ import { monomitter, Monomitter } from '@darkforest_eth/events'; import { CursorState, ModalId, ModalManagerEvent, ModalPosition, WorldCoords, } from '@darkforest_eth/types'; import { EventEmitter } from 'events'; import type PersistentChunkStore from '../../Backend/Storage/PersistentChunkStore'; class ModalManager extends EventEmitter { static instance: ModalManager; private lastIndex: number; private cursorState: CursorState; private persistentChunkStore: PersistentChunkStore; private modalPositions: Map; public modalPositions$: Monomitter>; public readonly activeModalId$: Monomitter; public readonly modalPositionChanged$: Monomitter; private constructor( persistentChunkStore: PersistentChunkStore, modalPositions: Map ) { super(); this.lastIndex = 0; this.activeModalId$ = monomitter(true); this.modalPositionChanged$ = monomitter(); this.persistentChunkStore = persistentChunkStore; this.modalPositions = modalPositions; } public static async create(persistentChunkStore: PersistentChunkStore): Promise { const modalPositions = await persistentChunkStore.loadModalPositions(); return new ModalManager(persistentChunkStore, modalPositions); } public getIndex(): number { this.lastIndex++; return this.lastIndex; } public getCursorState(): CursorState { return this.cursorState; } public setCursorState(newstate: CursorState): void { this.cursorState = newstate; this.emit(ModalManagerEvent.StateChanged, newstate); } public acceptInputForTarget(input: WorldCoords): void { if (this.cursorState !== CursorState.TargetingExplorer) return; this.emit(ModalManagerEvent.MiningCoordsUpdate, input); this.setCursorState(CursorState.Normal); } public getModalPosition(modalId: ModalId): ModalPosition | undefined { return this.modalPositions.get(modalId); } public getModalPositions(modalIds: ModalId[] = []): Map { if (modalIds.length === 0) return this.modalPositions; return modalIds.reduce>((acc, cur) => { const winPos = this.modalPositions.get(cur); if (!winPos) return acc; return acc.set(cur, winPos); }, new Map()); } public clearModalPosition(modalId: ModalId): void { this.modalPositions.delete(modalId); this.persistentChunkStore.saveModalPositions(this.modalPositions); this.modalPositionChanged$.publish(modalId); } public setModalPosition(modalId: ModalId, pos: ModalPosition): void { this.modalPositions.set(modalId, pos); this.persistentChunkStore.saveModalPositions(this.modalPositions); this.modalPositionChanged$.publish(modalId); } public setModalState(modalId: ModalId, state: ModalPosition['state']): void { const pos = this.modalPositions.get(modalId); if (pos) { this.setModalPosition(modalId, { ...pos, state, }); } else { this.setModalPosition(modalId, { modalId, state, }); } } } export default ModalManager; ================================================ FILE: src/Frontend/Game/NotificationManager.tsx ================================================ import { biomeName, isLocatable } from '@darkforest_eth/gamelogic'; import { Artifact, Biome, Chunk, ContractMethodName, EthTxStatus, LocatablePlanet, Planet, TxIntent, } from '@darkforest_eth/types'; import EventEmitter from 'events'; import { startCase } from 'lodash'; import React from 'react'; import { getRandomActionId } from '../../Backend/Utils/Utils'; import { ArtifactFound, ArtifactProspected, FoundCorrupted, FoundDeadSpace, FoundDeepSpace, FoundDesert, FoundForest, FoundGrassland, FoundIce, FoundLava, FoundOcean, FoundPirates, FoundRuins, FoundSilver, FoundSpace, FoundSwamp, FoundTradingPost, FoundTundra, FoundWasteland, Generic, PlanetAttacked, PlanetConquered, PlanetLost, Quasar, TxDeclined, } from '../Components/Icons'; import { ArtifactBiomeText, ArtifactRarityLabelAnim, ArtifactTypeText, } from '../Components/Labels/ArtifactLabels'; import { ArtifactNameLink, CenterChunkLink, FAQ04Link, PlanetNameLink } from '../Components/Text'; export const enum NotificationType { Tx, CanUpgrade, BalanceEmpty, WelcomePlayer, FoundSpace, FoundDeepSpace, FoundDeadSpace, FoundPirates, FoundSilver, FoundSilverBank, FoundTradingPost, FoundComet, FoundFoundry, FoundBiome, FoundBiomeOcean, FoundBiomeForest, FoundBiomeGrassland, FoundBiomeTundra, FoundBiomeSwamp, FoundBiomeDesert, FoundBiomeIce, FoundBiomeWasteland, FoundBiomeLava, FoundBiomeCorrupted, PlanetLost, PlanetWon, PlanetAttacked, ArtifactProspected, ArtifactFound, ReceivedPlanet, Generic, TxInitError, } const BiomeNotificationMap = { [Biome.OCEAN]: NotificationType.FoundBiomeOcean, [Biome.FOREST]: NotificationType.FoundBiomeForest, [Biome.GRASSLAND]: NotificationType.FoundBiomeGrassland, [Biome.TUNDRA]: NotificationType.FoundBiomeTundra, [Biome.SWAMP]: NotificationType.FoundBiomeSwamp, [Biome.DESERT]: NotificationType.FoundBiomeDesert, [Biome.ICE]: NotificationType.FoundBiomeIce, [Biome.WASTELAND]: NotificationType.FoundBiomeWasteland, [Biome.LAVA]: NotificationType.FoundBiomeLava, [Biome.CORRUPTED]: NotificationType.FoundBiomeCorrupted, }; function getNotificationTypeFromPlanetBiome(biome: Biome): NotificationType { if (!biome) throw new Error('Biome is a required to get a NotificationType'); return BiomeNotificationMap[biome]; } export type NotificationInfo = { type: NotificationType; message: React.ReactNode; icon: React.ReactNode; id: string; color?: string; txData?: TxIntent; txStatus?: EthTxStatus; }; export const enum NotificationManagerEvent { Notify = 'Notify', ClearNotification = 'ClearNotification', } class NotificationManager extends EventEmitter { static instance: NotificationManager; private constructor() { super(); } static getInstance(): NotificationManager { if (!NotificationManager.instance) { NotificationManager.instance = new NotificationManager(); } return NotificationManager.instance; } private getIcon(type: NotificationType) { switch (type) { case NotificationType.TxInitError: return ; case NotificationType.FoundSilverBank: return ; break; case NotificationType.FoundSpace: return ; break; case NotificationType.FoundDeepSpace: return ; break; case NotificationType.FoundDeadSpace: return ; break; case NotificationType.FoundPirates: return ; break; case NotificationType.FoundSilver: return ; break; case NotificationType.FoundTradingPost: return ; break; case NotificationType.FoundFoundry: return ; break; case NotificationType.FoundBiomeOcean: return ; break; case NotificationType.FoundBiomeForest: return ; break; case NotificationType.FoundBiomeGrassland: return ; break; case NotificationType.FoundBiomeTundra: return ; break; case NotificationType.FoundBiomeSwamp: return ; break; case NotificationType.FoundBiomeDesert: return ; break; case NotificationType.FoundBiomeIce: return ; break; case NotificationType.FoundBiomeWasteland: return ; break; case NotificationType.FoundBiomeLava: return ; break; case NotificationType.FoundBiomeCorrupted: return ; break; case NotificationType.PlanetAttacked: return ; break; case NotificationType.PlanetLost: return ; break; case NotificationType.PlanetWon: return ; break; case NotificationType.ArtifactProspected: return ; break; case NotificationType.ArtifactFound: return ; default: return ; break; } } reallyLongNotification() { let message = ''; for (let i = 0; i < 100; i++) { message += 'lol '; } this.emit(NotificationManagerEvent.Notify, { type: NotificationType.Generic, message, id: getRandomActionId(), icon: this.getIcon(NotificationType.Generic), }); } clearNotification(id: string) { this.emit(NotificationManagerEvent.ClearNotification, id); } notify(type: NotificationType, message: React.ReactNode): void { this.emit(NotificationManagerEvent.Notify, { type, message, id: getRandomActionId(), icon: this.getIcon(type), }); } welcomePlayer(): void { this.notify( NotificationType.WelcomePlayer, Welcome to the world to Dark Forest! These are your notifications.
Click a notification to dismiss it.
); } foundSpace(chunk: Chunk): void { this.notify( NotificationType.FoundSpace, Congrats! You found space! Space has more valuable resources than
the nebula where your home planet is located.{' '} Click to view.
); } foundDeepSpace(chunk: Chunk): void { this.notify( NotificationType.FoundDeepSpace, Congrats! You found deep space! Deep space has more rare
planets, but all planets in deep space have lowered defense!{' '} Click to view.
); } foundDeadSpace(chunk: Chunk): void { this.notify( NotificationType.FoundDeadSpace, Congrats! You found dead space! Dead space is the most valuable
and most dangerous part of the universe, where corrupted planets lie...{' '} Click to view.
); } foundSilver(planet: Planet): void { this.notify( NotificationType.FoundSilver, You found a silver mine! Silver can be used to upgrade planets.
Click to view .
); } foundSilverBank(planet: Planet): void { this.notify( NotificationType.FoundSilverBank, You found a quasar! Quasars are weak, but can hold a lot of silver.
Click to view .
); } foundTradingPost(planet: Planet): void { this.notify( NotificationType.FoundTradingPost, You found a spacetime rip! Now you can move artifacts in and out of the universe. Click to view . ); } foundPirates(planet: Planet): void { this.notify( NotificationType.FoundPirates, You found space pirates! Unconquered planets must be defeated first.
Click to view .
); } foundComet(planet: Planet): void { this.notify( NotificationType.FoundComet, You found a comet! Planets with comets have a stat doubled!
Click to view
); } foundBiome(planet: LocatablePlanet): void { this.notify( getNotificationTypeFromPlanetBiome(planet.biome), You have discovered the {biomeName(planet.biome)} biome!
Click to view
); } foundFoundry(planet: LocatablePlanet): void { this.notify( NotificationType.FoundFoundry, You have found a planet that can produce an artifact! Artifacts can be used to power up your planets and moves!
Click to view
); } artifactProspected(planet: LocatablePlanet): void { this.notify( NotificationType.ArtifactProspected, You prospected a Foundry!
What artifacts are waiting to be found on it? Click to view{' '}
); } artifactFound(planet: LocatablePlanet, artifact: Artifact): void { this.notify( NotificationType.ArtifactFound, You have found , a{' '} {' '} {'!'.repeat(artifact.rarity)}
Click to view
); } planetConquered(planet: LocatablePlanet): void { this.notify( NotificationType.PlanetWon, You conquered , you're unstoppable! ); } planetLost(planet: LocatablePlanet): void { this.notify( NotificationType.PlanetLost, You lost , oh no! ); } planetAttacked(planet: LocatablePlanet): void { this.notify( NotificationType.PlanetAttacked, Your Planet has been attacked! ); } planetCanUpgrade(planet: Planet): void { this.notify( NotificationType.CanUpgrade, Your planet can upgrade!
); } balanceEmpty(): void { this.notify( NotificationType.BalanceEmpty, Your xDAI account is out of balance!
Click here to learn how to get more.
); } receivedPlanet(planet: Planet) { this.notify( NotificationType.ReceivedPlanet, Someone just sent you their planet: .{' '} {!isLocatable(planet) && "You'll need to ask the person who sent it for its location."} ); } txInitError(methodName: ContractMethodName, failureReason: string) { this.notify( NotificationType.TxInitError, {startCase(methodName)} failed. Reason: {failureReason} ); } } export default NotificationManager; ================================================ FILE: src/Frontend/Game/Popups.ts ================================================ import { EthConnection, isPurchase, weiToEth } from '@darkforest_eth/network'; import { EthAddress, Setting, TransactionId, TxIntent } from '@darkforest_eth/types'; import { BigNumber as EthersBN, providers } from 'ethers'; import { getBooleanSetting } from '../Utils/SettingsHooks'; // tx is killed if user doesn't click popup within 20s const POPUP_TIMEOUT = 20000; interface OpenConfirmationConfig { contractAddress: EthAddress; connection: EthConnection; id: TransactionId; intent: TxIntent; overrides?: providers.TransactionRequest; from: EthAddress; gasFeeGwei: EthersBN; } export async function openConfirmationWindowForTransaction({ contractAddress, connection, id, intent, overrides, from, gasFeeGwei, }: OpenConfirmationConfig): Promise { const config = { contractAddress, account: connection.getAddress(), }; const autoApprove = getBooleanSetting(config, Setting.AutoApproveNonPurchaseTransactions); if (!autoApprove || isPurchase(overrides)) { localStorage.setItem(`${from}-gasFeeGwei`, gasFeeGwei.toString()); const account = connection.getAddress(); if (!account) throw new Error('no account'); const balanceEth = weiToEth(await connection.loadBalance(account)); const method = intent.methodName; const popup = window.open( `/wallet/${contractAddress}/${from}/${id}/${balanceEth}/${method}`, 'confirmationwindow', 'width=600,height=500' ); if (popup) { const opened = Date.now(); await new Promise((resolve, reject) => { const interval = setInterval(() => { if (popup.closed) { const approved = localStorage.getItem(`tx-approved-${from}-${id}`) === 'true'; if (approved) { resolve(); } else { reject(new Error('User rejected transaction.')); } localStorage.removeItem(`tx-approved-${from}-${id}`); clearInterval(interval); } else { if (Date.now() > opened + POPUP_TIMEOUT) { reject(new Error('Approval window popup timed out; check your popups!')); localStorage.removeItem(`tx-approved-${from}-${id}`); clearInterval(interval); popup.close(); } } }, 100); }); } else { throw new Error( "Please enable popups to confirm this transaction. After you've done so, try again." ); } } } ================================================ FILE: src/Frontend/Game/Viewport.ts ================================================ import { isLocatable } from '@darkforest_eth/gamelogic'; import { CanvasCoords, Chunk, DiagnosticUpdater, Planet, WorldCoords } from '@darkforest_eth/types'; import autoBind from 'auto-bind'; import GameUIManager from '../../Backend/GameLogic/GameUIManager'; import { distL2, vectorLength } from '../../Backend/Utils/Coordinates'; import UIEmitter, { UIEmitterEvent } from '../Utils/UIEmitter'; export const getDefaultScroll = (): number => { const isFirefox = navigator.userAgent.indexOf('Firefox') > 0; return isFirefox ? 1.005 : 1.0006; }; type ViewportData = { widthInWorldUnits: number; centerWorldCoords: WorldCoords; }; // multiplied by init velocity, since mousemove won't always happen at the same rate as rAF const BASE_VEL = 1; // if vel gets below this, kill it const VEL_THRESHOLD = 0.05; class Viewport { // The sole listener for events from Canvas // Handles panning and zooming // Handles reports of user interaction in canvas coords, transforms these events to world coords and filters when necessary, // and sends events up to GameUIManager static instance: Viewport | undefined; // the following two fields represent the position of the vieport in the world centerWorldCoords: WorldCoords; widthInWorldUnits: number; heightInWorldUnits: number; viewportWidth: number; // pixels viewportHeight: number; // pixels isPanning = false; mouseLastCoords: CanvasCoords | undefined; canvas: HTMLCanvasElement; isFirefox: boolean; gameUIManager: GameUIManager; mousedownCoords: CanvasCoords | undefined = undefined; // for momentum stuff velocity: WorldCoords | undefined = undefined; momentum = false; mouseSensitivity: number; intervalId: ReturnType; frameRequestId: number; diagnosticUpdater?: DiagnosticUpdater; scale: number; private isSending = false; private constructor( gameUIManager: GameUIManager, centerWorldCoords: WorldCoords, widthInWorldUnits: number, viewportWidth: number, viewportHeight: number, canvas: HTMLCanvasElement ) { this.gameUIManager = gameUIManager; // each of these is measured relative to the world coordinate system this.centerWorldCoords = centerWorldCoords; this.widthInWorldUnits = widthInWorldUnits; this.heightInWorldUnits = (widthInWorldUnits * viewportHeight) / viewportWidth; // while all of the above are in the world coordinate system, the below are in the page coordinate system this.viewportWidth = viewportWidth; // width / height this.viewportHeight = viewportHeight; this.mouseLastCoords = centerWorldCoords; this.canvas = canvas; const scroll = localStorage.getItem('scrollSpeed'); if (scroll) { this.mouseSensitivity = parseFloat(scroll); } else { this.mouseSensitivity = getDefaultScroll(); } this.isPanning = false; autoBind(this); // fixes issue where viewport inits weirdly - TODO figure out why this.setWorldWidth(this.widthInWorldUnits); this.onScroll(0); } public setDiagnosticUpdater(diagnosticUpdater: DiagnosticUpdater) { this.diagnosticUpdater = diagnosticUpdater; } onSendInit() { this.isSending = true; } onSendComplete() { this.isSending = false; } get minWorldWidth(): number { // TODO: Figure out a better way to set this based on viewport return 2; } get maxWorldWidth(): number { return this.gameUIManager.getWorldRadius() * 4; } public getViewportPosition() { return { ...this.centerWorldCoords }; } public getBottomBound() { return this.centerWorldCoords.y - this.heightInWorldUnits / 2; } public getLeftBound() { return this.centerWorldCoords.x - this.widthInWorldUnits / 2; } public getTopBound() { return this.centerWorldCoords.y + this.heightInWorldUnits / 2; } public getRightBound() { return this.centerWorldCoords.x + this.widthInWorldUnits / 2; } public getViewportWorldWidth() { return this.widthInWorldUnits; } public getViewportWorldHeight() { return this.heightInWorldUnits; } public setMouseSensitivty(mouseSensitivity: number) { this.mouseSensitivity = 1 + mouseSensitivity; localStorage.setItem('scrollSpeed', this.mouseSensitivity.toString()); } static getInstance(): Viewport { if (!Viewport.instance) { throw new Error('Attempted to get Viewport object before initialized'); } return Viewport.instance; } static destroyInstance(): void { const uiEmitter = UIEmitter.getInstance(); const viewport = Viewport.instance; if (viewport) { uiEmitter .removeListener(UIEmitterEvent.CanvasMouseDown, viewport.onMouseDown) .removeListener(UIEmitterEvent.CanvasMouseMove, viewport.onMouseMove) .removeListener(UIEmitterEvent.CanvasMouseUp, viewport.onMouseUp) .removeListener(UIEmitterEvent.CanvasMouseOut, viewport.onMouseOut) .removeListener(UIEmitterEvent.CanvasScroll, viewport.onScroll) .removeListener(UIEmitterEvent.WindowResize, viewport.onWindowResize) .removeListener(UIEmitterEvent.CenterPlanet, viewport.centerPlanet) .removeListener(UIEmitterEvent.ZoomIn, viewport.zoomIn) .removeListener(UIEmitterEvent.ZoomOut, viewport.zoomOut) .removeAllListeners(UIEmitterEvent.SendInitiated) .removeAllListeners(UIEmitterEvent.SendCancelled) .removeAllListeners(UIEmitterEvent.SendCompleted) .removeAllListeners(UIEmitterEvent.WindowResize); clearInterval(viewport.intervalId); window.cancelAnimationFrame(viewport.frameRequestId); } Viewport.instance = undefined; } static initialize( gameUIManager: GameUIManager, widthInWorldUnits: number, canvas: HTMLCanvasElement ): Viewport { const uiEmitter = UIEmitter.getInstance(); const homeCoords = gameUIManager.getHomeCoords(); const viewport = new Viewport( gameUIManager, homeCoords, widthInWorldUnits, canvas.width, canvas.height, canvas ); viewport.setDiagnosticUpdater(gameUIManager); // set starting position based on storage const stored = viewport.getStorage(); if (!stored) { viewport.zoomPlanet(gameUIManager.getHomePlanet()); } else { viewport.setData(stored); } uiEmitter .on(UIEmitterEvent.CanvasMouseDown, viewport.onMouseDown) .on(UIEmitterEvent.CanvasMouseMove, viewport.onMouseMove) .on(UIEmitterEvent.CanvasMouseUp, viewport.onMouseUp) .on(UIEmitterEvent.CanvasMouseOut, viewport.onMouseOut) .on(UIEmitterEvent.CanvasScroll, viewport.onScroll) .on(UIEmitterEvent.WindowResize, viewport.onWindowResize) .on(UIEmitterEvent.CenterPlanet, viewport.centerPlanet) .on(UIEmitterEvent.ZoomIn, viewport.zoomIn) .on(UIEmitterEvent.ZoomOut, viewport.zoomOut) .on(UIEmitterEvent.SendInitiated, viewport.onSendInit) .on(UIEmitterEvent.SendCancelled, viewport.onSendComplete) .on(UIEmitterEvent.SendCompleted, viewport.onSendComplete) .on(UIEmitterEvent.WindowResize, viewport.onResize); viewport.intervalId = setInterval(viewport.setStorage, 5000); Viewport.instance = viewport; return viewport; } onResize() { this.onScroll(0); } private getStorageKey(): string { const acc = this.gameUIManager.getAccount(); const addr = this.gameUIManager.getContractAddress(); return `${acc}-${addr}-viewport`; } // returns this viewport's ViewportData, which will let us initialize it at the same zoom / pos getStorage(): ViewportData | undefined { const key = this.getStorageKey(); const stored = localStorage.getItem(key); if (stored) return (JSON.parse(stored) as ViewportData) || undefined; return undefined; } // stores this viewport's ViewportData into storage setStorage() { const key = this.getStorageKey(); const data: ViewportData = { widthInWorldUnits: this.widthInWorldUnits, centerWorldCoords: this.centerWorldCoords, }; localStorage.setItem(key, JSON.stringify(data)); } // set this viewport's zoom and pos to the given ViewportData setData(data: ViewportData) { // lets us prevent the program from crashing if this was called poorly typeof data.widthInWorldUnits === 'number' && this.setWorldWidth(data.widthInWorldUnits); typeof data.widthInWorldUnits === 'number' && this.centerCoords(data.centerWorldCoords); } centerPlanet(planet: Planet | undefined): void { if (planet && isLocatable(planet)) { this.centerCoords(planet.location.coords); } } // centers on a planet and makes it fill the viewport zoomPlanet(planet?: Planet, radii?: number): void { if (!planet || !isLocatable(planet)) return; this.centerPlanet(planet); // in world coords const rad = this.gameUIManager.getRadiusOfPlanetLevel(planet.planetLevel); if (radii !== undefined) { this.setWorldHeight(radii * rad); } } centerCoords(coords: WorldCoords): void { if (typeof coords.x !== 'number' || typeof coords.y !== 'number') throw new Error('please pass in an object that looks like {x: 0, y: 0}'); this.centerWorldCoords = { ...coords }; } centerChunk(chunk: Chunk): void { const { bottomLeft, sideLength } = chunk.chunkFootprint; this.centerWorldCoords = { x: bottomLeft.x + sideLength / 2, y: bottomLeft.y + sideLength / 2, }; } zoomIn(): void { this.onScroll(-600, true); } zoomOut(): void { this.onScroll(600, true); } // Event handlers onMouseDown(canvasCoords: CanvasCoords) { if (this.isSending) return; this.mousedownCoords = canvasCoords; const uiManager = this.gameUIManager; const uiEmitter = UIEmitter.getInstance(); const worldCoords = this.canvasToWorldCoords(canvasCoords); if (!uiManager.isOverOwnPlanet(worldCoords)) { this.isPanning = true; } uiEmitter.emit(UIEmitterEvent.WorldMouseDown, worldCoords); this.mouseLastCoords = canvasCoords; this.momentum = false; this.velocity = undefined; } onMouseMove(canvasCoords: CanvasCoords) { const uiEmitter = UIEmitter.getInstance(); if (this.isPanning && this.mouseLastCoords) { // if panning, don't need to emit mouse move event const dx = this.scale * (this.mouseLastCoords.x - canvasCoords.x); const dy = -this.scale * (this.mouseLastCoords.y - canvasCoords.y); this.velocity = { x: dx * BASE_VEL, y: dy * BASE_VEL }; this.centerWorldCoords.x += dx; this.centerWorldCoords.y += dy; } else { const worldCoords = this.canvasToWorldCoords(canvasCoords); uiEmitter.emit(UIEmitterEvent.WorldMouseMove, worldCoords); } this.mouseLastCoords = canvasCoords; } onMouseUp(canvasCoords: CanvasCoords) { const uiEmitter = UIEmitter.getInstance(); const worldCoords = this.canvasToWorldCoords(canvasCoords); if (this.mousedownCoords && distL2(canvasCoords, this.mousedownCoords) < 3) { uiEmitter.emit(UIEmitterEvent.WorldMouseClick, worldCoords); } this.mousedownCoords = undefined; uiEmitter.emit(UIEmitterEvent.WorldMouseUp, worldCoords); this.isPanning = false; this.momentum = true; this.mouseLastCoords = canvasCoords; if (this.velocity && vectorLength(this.velocity) < VEL_THRESHOLD) { this.velocity = undefined; this.momentum = false; } } onMouseOut() { const uiEmitter = UIEmitter.getInstance(); uiEmitter.emit(UIEmitterEvent.WorldMouseOut); this.isPanning = false; this.mouseLastCoords = undefined; } onScroll(deltaY: number, forceZoom = false) { if (this.mouseLastCoords !== undefined || forceZoom) { const base = this.mouseSensitivity; const newWidth = this.widthInWorldUnits * base ** deltaY; if (!this.isValidWorldWidth(newWidth)) { return; } let mouseWorldCoords = this.centerWorldCoords; if (this.mouseLastCoords) { mouseWorldCoords = this.canvasToWorldCoords(this.mouseLastCoords); } const centersDiff = { x: this.centerWorldCoords.x - mouseWorldCoords.x, y: this.centerWorldCoords.y - mouseWorldCoords.y, }; const newCentersDiff = { x: centersDiff.x * base ** deltaY, y: centersDiff.y * base ** deltaY, }; const newCenter = { x: mouseWorldCoords.x + newCentersDiff.x, y: mouseWorldCoords.y + newCentersDiff.y, }; this.centerWorldCoords.x = newCenter.x; this.centerWorldCoords.y = newCenter.y; this.setWorldWidth(newWidth); } } onWindowResize() { this.viewportHeight = this.canvas.height; this.viewportWidth = this.canvas.width; this.scale = this.widthInWorldUnits / this.viewportWidth; } // Camera utility functions canvasToWorldCoords(canvasCoords: CanvasCoords): WorldCoords { const worldX = this.canvasToWorldX(canvasCoords.x); const worldY = this.canvasToWorldY(canvasCoords.y); return { x: worldX, y: worldY }; } worldToCanvasCoords(worldCoords: WorldCoords): CanvasCoords { const canvasX = this.worldToCanvasX(worldCoords.x); const canvasY = this.worldToCanvasY(worldCoords.y); return { x: canvasX, y: canvasY }; } public worldToCanvasDist(d: number): number { return d / this.scale; } public canvasToWorldDist(d: number): number { return d * this.scale; } private worldToCanvasX(x: number): number { return (x - this.centerWorldCoords.x) / this.scale + this.viewportWidth / 2; } private canvasToWorldX(x: number): number { return (x - this.viewportWidth / 2) * this.scale + this.centerWorldCoords.x; } private worldToCanvasY(y: number): number { return (-1 * (y - this.centerWorldCoords.y)) / this.scale + this.viewportHeight / 2; } private canvasToWorldY(y: number): number { return -1 * (y - this.viewportHeight / 2) * this.scale + this.centerWorldCoords.y; } public isInOrAroundViewport(coords: WorldCoords): boolean { if (Math.abs(coords.x - this.centerWorldCoords.x) > 0.6 * this.widthInWorldUnits) { return false; } if (Math.abs(coords.y - this.centerWorldCoords.y) > 0.6 * this.heightInWorldUnits) { return false; } return true; } public isInViewport(coords: WorldCoords) { return ( coords.x >= this.centerWorldCoords.x - this.widthInWorldUnits / 2 && coords.x <= this.centerWorldCoords.x + this.widthInWorldUnits / 2 && coords.y >= this.centerWorldCoords.y - this.heightInWorldUnits / 2 && coords.y <= this.centerWorldCoords.y + this.heightInWorldUnits / 2 ); } public intersectsViewport(chunk: Chunk): boolean { const chunkLeft = chunk.chunkFootprint.bottomLeft.x; const chunkRight = chunkLeft + chunk.chunkFootprint.sideLength; const chunkBottom = chunk.chunkFootprint.bottomLeft.y; const chunkTop = chunkBottom + chunk.chunkFootprint.sideLength; const viewportLeft = this.centerWorldCoords.x - this.widthInWorldUnits / 2; const viewportRight = this.centerWorldCoords.x + this.widthInWorldUnits / 2; const viewportBottom = this.centerWorldCoords.y - this.heightInWorldUnits / 2; const viewportTop = this.centerWorldCoords.y + this.heightInWorldUnits / 2; if ( chunkLeft > viewportRight || chunkRight < viewportLeft || chunkBottom > viewportTop || chunkTop < viewportBottom ) { return false; } return true; } private isValidWorldWidth(width: number) { return width >= this.minWorldWidth && width <= this.maxWorldWidth; } private setWorldWidth(width: number): void { if (this.isValidWorldWidth(width)) { this.widthInWorldUnits = width; this.heightInWorldUnits = (width * this.viewportHeight) / this.viewportWidth; this.scale = this.widthInWorldUnits / this.viewportWidth; this.updateDiagnostics(); } } public setWorldHeight(height: number): void { this.heightInWorldUnits = height; this.widthInWorldUnits = (height * this.viewportWidth) / this.viewportHeight; this.updateDiagnostics(); } private updateDiagnostics() { this.diagnosticUpdater?.updateDiagnostics( (d) => (d.width = Math.floor(this.widthInWorldUnits)) ); this.diagnosticUpdater?.updateDiagnostics( (d) => (d.height = Math.floor(this.heightInWorldUnits)) ); } } export default Viewport; ================================================ FILE: src/Frontend/Pages/App.tsx ================================================ import { CONTRACT_ADDRESS } from '@darkforest_eth/contracts'; import { address } from '@darkforest_eth/serde'; import React from 'react'; import { BrowserRouter as Router, Redirect, Route, Switch } from 'react-router-dom'; import { createGlobalStyle } from 'styled-components'; import { Theme } from '../Components/Theme'; import { LandingPageBackground } from '../Renderers/LandingPageCanvas'; import dfstyles from '../Styles/dfstyles'; import { CreateLobby } from './CreateLobby'; import { EventsPage } from './EventsPage'; import { GameLandingPage } from './GameLandingPage'; import { GifMaker } from './GifMaker'; import LandingPage from './LandingPage'; import { NotFoundPage } from './NotFoundPage'; import { ShareArtifact } from './ShareArtifact'; import { SharePlanet } from './SharePlanet'; import { TestArtifactImages } from './TestArtifactImages'; import { TxConfirmPopup } from './TxConfirmPopup'; import UnsubscribePage from './UnsubscribePage'; import { ValhallaPage } from './ValhallaPage'; const isProd = process.env.NODE_ENV === 'production'; const defaultAddress = address(CONTRACT_ADDRESS); function App() { return ( <> {/* Provides theming for WebComponents from the `@darkforest_eth/ui` package */} {!isProd && } {!isProd && } {!isProd && } ); } const GlobalStyle = createGlobalStyle` body { width: 100vw; min-height: 100vh; background-color: ${dfstyles.colors.background}; } `; export default App; ================================================ FILE: src/Frontend/Pages/CreateLobby.tsx ================================================ import { INIT_ADDRESS } from '@darkforest_eth/contracts'; // This is loaded as URL paths by a webpack loader import initContractAbiUrl from '@darkforest_eth/contracts/abis/DFInitialize.json'; import { EthConnection } from '@darkforest_eth/network'; import { address } from '@darkforest_eth/serde'; import { ArtifactRarity, EthAddress, UnconfirmedCreateLobby } from '@darkforest_eth/types'; import { Contract } from 'ethers'; import _ from 'lodash'; import React, { useCallback, useEffect, useMemo, useState } from 'react'; import { RouteComponentProps } from 'react-router-dom'; import { ContractsAPI, makeContractsAPI } from '../../Backend/GameLogic/ContractsAPI'; import { ContractsAPIEvent } from '../../_types/darkforest/api/ContractsAPITypes'; import { InitRenderState, Wrapper } from '../Components/GameLandingPageComponents'; import { ConfigurationPane } from '../Panes/Lobbies/ConfigurationPane'; import { Minimap } from '../Panes/Lobbies/MinimapPane'; import { MinimapConfig } from '../Panes/Lobbies/MinimapUtils'; import { LobbyInitializers } from '../Panes/Lobbies/Reducer'; import { listenForKeyboardEvents, unlinkKeyboardEvents } from '../Utils/KeyEmitters'; import { CadetWormhole } from '../Views/CadetWormhole'; import { LobbyLandingPage } from './LobbyLandingPage'; type ErrorState = | { type: 'invalidAddress' } | { type: 'contractLoad' } | { type: 'invalidContract' } | { type: 'invalidCreate' }; export function CreateLobby({ match }: RouteComponentProps<{ contract: string }>) { const [connection, setConnection] = useState(); const [ownerAddress, setOwnerAddress] = useState(); const [contract, setContract] = useState(); const [startingConfig, setStartingConfig] = useState(); const [lobbyAddress, setLobbyAddress] = useState(); const [minimapConfig, setMinimapConfig] = useState(); const onMapChange = useMemo(() => { return _.debounce((config: MinimapConfig) => setMinimapConfig(config), 500); }, [setMinimapConfig]); let contractAddress: EthAddress | undefined; try { contractAddress = address(match.params.contract); } catch (err) { console.error('Invalid address', err); } const [errorState, setErrorState] = useState( contractAddress ? undefined : { type: 'invalidAddress' } ); useEffect(() => { listenForKeyboardEvents(); return () => unlinkKeyboardEvents(); }, []); const onReady = useCallback( (connection: EthConnection) => { setConnection(connection); setOwnerAddress(connection.getAddress()); }, [setConnection] ); useEffect(() => { if (connection && contractAddress) { makeContractsAPI({ connection, contractAddress }) .then((contract) => setContract(contract)) .catch((e) => { console.log(e); setErrorState({ type: 'contractLoad' }); }); } }, [connection, contractAddress]); useEffect(() => { if (contract) { contract .getConstants() .then((config) => { setStartingConfig({ // Explicitly defaulting this to false WHITELIST_ENABLED: false, // TODO: Figure out if we should expose this from contract START_PAUSED: false, ADMIN_CAN_ADD_PLANETS: config.ADMIN_CAN_ADD_PLANETS, WORLD_RADIUS_LOCKED: config.WORLD_RADIUS_LOCKED, WORLD_RADIUS_MIN: config.WORLD_RADIUS_MIN, DISABLE_ZK_CHECKS: config.DISABLE_ZK_CHECKS, PLANETHASH_KEY: config.PLANETHASH_KEY, SPACETYPE_KEY: config.SPACETYPE_KEY, BIOMEBASE_KEY: config.BIOMEBASE_KEY, PERLIN_MIRROR_X: config.PERLIN_MIRROR_X, PERLIN_MIRROR_Y: config.PERLIN_MIRROR_Y, PERLIN_LENGTH_SCALE: config.PERLIN_LENGTH_SCALE, MAX_NATURAL_PLANET_LEVEL: config.MAX_NATURAL_PLANET_LEVEL, TIME_FACTOR_HUNDREDTHS: config.TIME_FACTOR_HUNDREDTHS, PERLIN_THRESHOLD_1: config.PERLIN_THRESHOLD_1, PERLIN_THRESHOLD_2: config.PERLIN_THRESHOLD_2, PERLIN_THRESHOLD_3: config.PERLIN_THRESHOLD_3, INIT_PERLIN_MIN: config.INIT_PERLIN_MIN, INIT_PERLIN_MAX: config.INIT_PERLIN_MAX, SPAWN_RIM_AREA: config.SPAWN_RIM_AREA, BIOME_THRESHOLD_1: config.BIOME_THRESHOLD_1, BIOME_THRESHOLD_2: config.BIOME_THRESHOLD_2, PLANET_LEVEL_THRESHOLDS: config.PLANET_LEVEL_THRESHOLDS, PLANET_RARITY: config.PLANET_RARITY, LOCATION_REVEAL_COOLDOWN: config.LOCATION_REVEAL_COOLDOWN, // TODO: Implement when we add this scoring contract back CLAIM_PLANET_COOLDOWN: 0, // TODO: Need to think through this implementation a bit more, even if only toggling planet types PLANET_TYPE_WEIGHTS: config.PLANET_TYPE_WEIGHTS, // TODO: Rename in one of the places // TODO: Implement... Needs a datetime input component (WIP) TOKEN_MINT_END_TIMESTAMP: 1948939200, // new Date("2031-10-05T04:00:00.000Z").getTime() / 1000, PHOTOID_ACTIVATION_DELAY: config.PHOTOID_ACTIVATION_DELAY, SILVER_SCORE_VALUE: config.SILVER_SCORE_VALUE, ARTIFACT_POINT_VALUES: [ config.ARTIFACT_POINT_VALUES[ArtifactRarity.Unknown], config.ARTIFACT_POINT_VALUES[ArtifactRarity.Common], config.ARTIFACT_POINT_VALUES[ArtifactRarity.Rare], config.ARTIFACT_POINT_VALUES[ArtifactRarity.Epic], config.ARTIFACT_POINT_VALUES[ArtifactRarity.Legendary], config.ARTIFACT_POINT_VALUES[ArtifactRarity.Mythic], ], PLANET_TRANSFER_ENABLED: config.PLANET_TRANSFER_ENABLED, SPACE_JUNK_ENABLED: config.SPACE_JUNK_ENABLED, SPACE_JUNK_LIMIT: config.SPACE_JUNK_LIMIT, PLANET_LEVEL_JUNK: config.PLANET_LEVEL_JUNK, ABANDON_SPEED_CHANGE_PERCENT: config.ABANDON_SPEED_CHANGE_PERCENT, ABANDON_RANGE_CHANGE_PERCENT: config.ABANDON_RANGE_CHANGE_PERCENT, CAPTURE_ZONES_ENABLED: config.CAPTURE_ZONES_ENABLED, CAPTURE_ZONE_CHANGE_BLOCK_INTERVAL: config.CAPTURE_ZONE_CHANGE_BLOCK_INTERVAL, CAPTURE_ZONE_COUNT: config.CAPTURE_ZONE_COUNT, CAPTURE_ZONE_PLANET_LEVEL_SCORE: config.CAPTURE_ZONE_PLANET_LEVEL_SCORE, CAPTURE_ZONE_RADIUS: config.CAPTURE_ZONE_RADIUS, CAPTURE_ZONE_HOLD_BLOCKS_REQUIRED: config.CAPTURE_ZONE_HOLD_BLOCKS_REQUIRED, CAPTURE_ZONES_PER_5000_WORLD_RADIUS: config.CAPTURE_ZONES_PER_5000_WORLD_RADIUS, }); }) .catch((e) => { console.log(e); setErrorState({ type: 'invalidContract' }); }); } }, [contract]); async function createLobby(config: LobbyInitializers) { if (!contract) { setErrorState({ type: 'invalidCreate' }); return; } const initializers = { ...startingConfig, ...config }; console.log(initializers); const InitABI = await fetch(initContractAbiUrl).then((r) => r.json()); const artifactBaseURI = ''; const initInterface = Contract.getInterface(InitABI); const initAddress = INIT_ADDRESS; const initFunctionCall = initInterface.encodeFunctionData('init', [ initializers.WHITELIST_ENABLED, artifactBaseURI, initializers, ]); const txIntent: UnconfirmedCreateLobby = { methodName: 'createLobby', contract: contract.contract, args: Promise.resolve([initAddress, initFunctionCall]), }; contract.once(ContractsAPIEvent.LobbyCreated, (owner: EthAddress, lobby: EthAddress) => { if (owner === ownerAddress) { setLobbyAddress(lobby); } }); const tx = await contract.submitTransaction(txIntent, { // The createLobby function costs somewhere around 12mil gas gasLimit: '16777215', }); await tx.confirmedPromise; } if (errorState) { switch (errorState.type) { case 'contractLoad': return ; case 'invalidAddress': case 'invalidContract': return ; case 'invalidCreate': return ; default: // https://www.typescriptlang.org/docs/handbook/2/narrowing.html#exhaustiveness-checking const _exhaustive: never = errorState; return _exhaustive; } } let content; if (startingConfig) { content = ( <> {/* Minimap uses modalIndex=1 so it is always underneath the configuration pane */} ); } else { content = ; } return ( {content} ); } ================================================ FILE: src/Frontend/Pages/EventsPage.tsx ================================================ import React from 'react'; export function EventsPage() { return ( ); } ================================================ FILE: src/Frontend/Pages/GameLandingPage.tsx ================================================ import { BLOCK_EXPLORER_URL } from '@darkforest_eth/constants'; import { CONTRACT_ADDRESS } from '@darkforest_eth/contracts'; import { DarkForest } from '@darkforest_eth/contracts/typechain'; import { EthConnection, neverResolves, weiToEth } from '@darkforest_eth/network'; import { address } from '@darkforest_eth/serde'; import { bigIntFromKey } from '@darkforest_eth/whitelist'; import { utils, Wallet } from 'ethers'; import { reverse } from 'lodash'; import React, { useCallback, useEffect, useRef, useState } from 'react'; import { RouteComponentProps, useHistory } from 'react-router-dom'; import { makeContractsAPI } from '../../Backend/GameLogic/ContractsAPI'; import GameManager, { GameManagerEvent } from '../../Backend/GameLogic/GameManager'; import GameUIManager from '../../Backend/GameLogic/GameUIManager'; import TutorialManager, { TutorialState } from '../../Backend/GameLogic/TutorialManager'; import { addAccount, getAccounts } from '../../Backend/Network/AccountManager'; import { getEthConnection, loadDiamondContract } from '../../Backend/Network/Blockchain'; import { callRegisterAndWaitForConfirmation, EmailResponse, RegisterConfirmationResponse, requestDevFaucet, submitInterestedEmail, submitPlayerEmail, } from '../../Backend/Network/UtilityServerAPI'; import { getWhitelistArgs } from '../../Backend/Utils/WhitelistSnarkArgsHelper'; import { ZKArgIdx } from '../../_types/darkforest/api/ContractsAPITypes'; import { GameWindowWrapper, InitRenderState, TerminalToggler, TerminalWrapper, Wrapper, } from '../Components/GameLandingPageComponents'; import { MythicLabelText } from '../Components/Labels/MythicLabel'; import { TextPreview } from '../Components/TextPreview'; import { TopLevelDivProvider, UIManagerProvider } from '../Utils/AppHooks'; import { Incompatibility, unsupportedFeatures } from '../Utils/BrowserChecks'; import { TerminalTextStyle } from '../Utils/TerminalTypes'; import UIEmitter, { UIEmitterEvent } from '../Utils/UIEmitter'; import { GameWindowLayout } from '../Views/GameWindowLayout'; import { Terminal, TerminalHandle } from '../Views/Terminal'; const enum TerminalPromptStep { NONE, COMPATIBILITY_CHECKS_PASSED, DISPLAY_ACCOUNTS, GENERATE_ACCOUNT, IMPORT_ACCOUNT, ACCOUNT_SET, ASKING_HAS_WHITELIST_KEY, ASKING_WAITLIST_EMAIL, ASKING_WHITELIST_KEY, ASKING_PLAYER_EMAIL, FETCHING_ETH_DATA, ASK_ADD_ACCOUNT, ADD_ACCOUNT, NO_HOME_PLANET, SEARCHING_FOR_HOME_PLANET, ALL_CHECKS_PASS, COMPLETE, TERMINATED, ERROR, } export function GameLandingPage({ match, location }: RouteComponentProps<{ contract: string }>) { const history = useHistory(); const terminalHandle = useRef(); const gameUIManagerRef = useRef(); const topLevelContainer = useRef(null); const [gameManager, setGameManager] = useState(); const [terminalVisible, setTerminalVisible] = useState(true); const [initRenderState, setInitRenderState] = useState(InitRenderState.NONE); const [ethConnection, setEthConnection] = useState(); const [step, setStep] = useState(TerminalPromptStep.NONE); const params = new URLSearchParams(location.search); const useZkWhitelist = params.has('zkWhitelist'); const selectedAddress = params.get('account'); const contractAddress = address(match.params.contract); const isLobby = contractAddress !== address(CONTRACT_ADDRESS); useEffect(() => { getEthConnection() .then((ethConnection) => setEthConnection(ethConnection)) .catch((e) => { alert('error connecting to blockchain'); console.log(e); }); }, []); const isProd = process.env.NODE_ENV === 'production'; const advanceStateFromNone = useCallback( async (terminal: React.MutableRefObject) => { const issues = await unsupportedFeatures(); if (issues.includes(Incompatibility.MobileOrTablet)) { terminal.current?.println( 'ERROR: Mobile or tablet device detected. Please use desktop.', TerminalTextStyle.Red ); } if (issues.includes(Incompatibility.NoIDB)) { terminal.current?.println( 'ERROR: IndexedDB not found. Try using a different browser.', TerminalTextStyle.Red ); } if (issues.includes(Incompatibility.UnsupportedBrowser)) { terminal.current?.println( 'ERROR: Browser unsupported. Try Brave, Firefox, or Chrome.', TerminalTextStyle.Red ); } if (issues.length > 0) { terminal.current?.print( `${issues.length.toString()} errors found. `, TerminalTextStyle.Red ); terminal.current?.println('Please resolve them and refresh the page.'); setStep(TerminalPromptStep.ASKING_WAITLIST_EMAIL); } else { setStep(TerminalPromptStep.COMPATIBILITY_CHECKS_PASSED); } }, [] ); const advanceStateFromCompatibilityPassed = useCallback( async (terminal: React.MutableRefObject) => { if (isLobby) { terminal.current?.newline(); terminal.current?.printElement( ); terminal.current?.newline(); terminal.current?.newline(); } else { terminal.current?.newline(); terminal.current?.newline(); terminal.current?.printElement(); terminal.current?.newline(); terminal.current?.newline(); terminal.current?.print(' '); terminal.current?.print('Version', TerminalTextStyle.Sub); terminal.current?.print(' '); terminal.current?.print('Date', TerminalTextStyle.Sub); terminal.current?.print(' '); terminal.current?.print('Champion', TerminalTextStyle.Sub); terminal.current?.newline(); terminal.current?.print(' v0.1 ', TerminalTextStyle.Text); terminal.current?.print('02/05/2020 ', TerminalTextStyle.Text); terminal.current?.printLink( 'Dylan Field', () => { window.open('https://twitter.com/zoink'); }, TerminalTextStyle.Text ); terminal.current?.newline(); terminal.current?.print(' v0.2 ', TerminalTextStyle.Text); terminal.current?.println('06/06/2020 Nate Foss', TerminalTextStyle.Text); terminal.current?.print(' v0.3 ', TerminalTextStyle.Text); terminal.current?.print('08/07/2020 ', TerminalTextStyle.Text); terminal.current?.printLink( '@hideandcleanse', () => { window.open('https://twitter.com/hideandcleanse'); }, TerminalTextStyle.Text ); terminal.current?.newline(); terminal.current?.print(' v0.4 ', TerminalTextStyle.Text); terminal.current?.print('10/02/2020 ', TerminalTextStyle.Text); terminal.current?.printLink( 'Jacob Rosenthal', () => { window.open('https://twitter.com/jacobrosenthal'); }, TerminalTextStyle.Text ); terminal.current?.newline(); terminal.current?.print(' v0.5 ', TerminalTextStyle.Text); terminal.current?.print('12/25/2020 ', TerminalTextStyle.Text); terminal.current?.printElement( ); terminal.current?.println(''); terminal.current?.print(' v0.6 r1 ', TerminalTextStyle.Text); terminal.current?.print('05/22/2021 ', TerminalTextStyle.Text); terminal.current?.printLink( 'Ansgar Dietrichs', () => { window.open('https://twitter.com/adietrichs'); }, TerminalTextStyle.Text ); terminal.current?.newline(); terminal.current?.print(' v0.6 r2 ', TerminalTextStyle.Text); terminal.current?.print('06/28/2021 ', TerminalTextStyle.Text); terminal.current?.printLink( '@orden_gg', () => { window.open('https://twitter.com/orden_gg'); }, TerminalTextStyle.Text ); terminal.current?.newline(); terminal.current?.print(' v0.6 r3 ', TerminalTextStyle.Text); terminal.current?.print('08/22/2021 ', TerminalTextStyle.Text); terminal.current?.printLink( '@dropswap_gg', () => { window.open('https://twitter.com/dropswap_gg'); }, TerminalTextStyle.Text ); terminal.current?.newline(); terminal.current?.print(' v0.6 r4 ', TerminalTextStyle.Text); terminal.current?.print('10/01/2021 ', TerminalTextStyle.Text); terminal.current?.printLink( '@orden_gg', () => { window.open('https://twitter.com/orden_gg'); }, TerminalTextStyle.Text ); terminal.current?.newline(); terminal.current?.print(' v0.6 r5 ', TerminalTextStyle.Text); terminal.current?.print('02/18/2022 ', TerminalTextStyle.Text); terminal.current?.printLink( '@d_fdao', () => { window.open('https://twitter.com/d_fdao'); }, TerminalTextStyle.Text ); terminal.current?.print(' + '); terminal.current?.printLink( '@orden_gg', () => { window.open('https://twitter.com/orden_gg'); }, TerminalTextStyle.Text ); terminal.current?.newline(); terminal.current?.newline(); } const accounts = getAccounts(); terminal.current?.println(`Found ${accounts.length} accounts on this device.`); terminal.current?.println(``); if (accounts.length > 0) { terminal.current?.print('(a) ', TerminalTextStyle.Sub); terminal.current?.println('Login with existing account.'); } terminal.current?.print('(n) ', TerminalTextStyle.Sub); terminal.current?.println(`Generate new burner wallet account.`); terminal.current?.print('(i) ', TerminalTextStyle.Sub); terminal.current?.println(`Import private key.`); terminal.current?.println(``); terminal.current?.println(`Select an option:`, TerminalTextStyle.Text); if (selectedAddress !== null) { terminal.current?.println( `Selecting account ${selectedAddress} from url...`, TerminalTextStyle.Green ); // Search accounts backwards in case a player has used a private key more than once. // In that case, we want to take the most recently created account. const account = reverse(getAccounts()).find((a) => a.address === selectedAddress); if (!account) { terminal.current?.println('Unrecognized account found in url.', TerminalTextStyle.Red); return; } try { await ethConnection?.setAccount(account.privateKey); setStep(TerminalPromptStep.ACCOUNT_SET); } catch (e) { terminal.current?.println( 'An unknown error occurred. please try again.', TerminalTextStyle.Red ); } } else { const userInput = await terminal.current?.getInput(); if (userInput === 'a' && accounts.length > 0) { setStep(TerminalPromptStep.DISPLAY_ACCOUNTS); } else if (userInput === 'n') { setStep(TerminalPromptStep.GENERATE_ACCOUNT); } else if (userInput === 'i') { setStep(TerminalPromptStep.IMPORT_ACCOUNT); } else { terminal.current?.println('Unrecognized input. Please try again.'); await advanceStateFromCompatibilityPassed(terminal); } } }, [isLobby, ethConnection, selectedAddress] ); const advanceStateFromDisplayAccounts = useCallback( async (terminal: React.MutableRefObject) => { terminal.current?.println(``); const accounts = getAccounts(); for (let i = 0; i < accounts.length; i += 1) { terminal.current?.print(`(${i + 1}): `, TerminalTextStyle.Sub); terminal.current?.println(`${accounts[i].address}`); } terminal.current?.println(``); terminal.current?.println(`Select an account:`, TerminalTextStyle.Text); const selection = +((await terminal.current?.getInput()) || ''); if (isNaN(selection) || selection > accounts.length) { terminal.current?.println('Unrecognized input. Please try again.'); await advanceStateFromDisplayAccounts(terminal); } else { const account = accounts[selection - 1]; try { await ethConnection?.setAccount(account.privateKey); setStep(TerminalPromptStep.ACCOUNT_SET); } catch (e) { terminal.current?.println( 'An unknown error occurred. please try again.', TerminalTextStyle.Red ); } } }, [ethConnection] ); const advanceStateFromGenerateAccount = useCallback( async (terminal: React.MutableRefObject) => { const newWallet = Wallet.createRandom(); const newSKey = newWallet.privateKey; const newAddr = address(newWallet.address); try { addAccount(newSKey); ethConnection?.setAccount(newSKey); terminal.current?.println(``); terminal.current?.print(`Created new burner wallet with address `); terminal.current?.printElement(); terminal.current?.println(``); terminal.current?.println(''); terminal.current?.println( 'Note: Burner wallets are stored in local storage.', TerminalTextStyle.Text ); terminal.current?.println('They are relatively insecure and you should avoid '); terminal.current?.println('storing substantial funds in them.'); terminal.current?.println(''); terminal.current?.println('Also, clearing browser local storage/cache will render your'); terminal.current?.println( 'burner wallets inaccessible, unless you export your private keys.' ); terminal.current?.println(''); terminal.current?.println('Press any key to continue:', TerminalTextStyle.Text); await terminal.current?.getInput(); setStep(TerminalPromptStep.ACCOUNT_SET); } catch (e) { terminal.current?.println( 'An unknown error occurred. please try again.', TerminalTextStyle.Red ); } }, [ethConnection] ); const advanceStateFromImportAccount = useCallback( async (terminal: React.MutableRefObject) => { terminal.current?.println( 'Enter the 0x-prefixed private key of the account you wish to import', TerminalTextStyle.Text ); terminal.current?.println( "NOTE: THIS WILL STORE THE PRIVATE KEY IN YOUR BROWSER'S LOCAL STORAGE", TerminalTextStyle.Text ); terminal.current?.println( 'Local storage is relatively insecure. We recommend only importing accounts with zero-to-no funds.' ); const newSKey = (await terminal.current?.getInput()) || ''; try { const newAddr = address(utils.computeAddress(newSKey)); addAccount(newSKey); ethConnection?.setAccount(newSKey); terminal.current?.println(`Imported account with address ${newAddr}.`); setStep(TerminalPromptStep.ACCOUNT_SET); } catch (e) { terminal.current?.println( 'An unknown error occurred. please try again.', TerminalTextStyle.Red ); } }, [ethConnection] ); const advanceStateFromAccountSet = useCallback( async (terminal: React.MutableRefObject) => { try { const playerAddress = ethConnection?.getAddress(); if (!playerAddress || !ethConnection) throw new Error('not logged in'); const whitelist = await ethConnection.loadContract( contractAddress, loadDiamondContract ); const isWhitelisted = await whitelist.isWhitelisted(playerAddress); // TODO(#2329): isWhitelisted should just check the contractOwner const adminAddress = address(await whitelist.adminAddress()); terminal.current?.println(''); terminal.current?.print('Checking if whitelisted... '); // TODO(#2329): isWhitelisted should just check the contractOwner if (isWhitelisted || playerAddress === adminAddress) { terminal.current?.println('Player whitelisted.'); terminal.current?.println(''); terminal.current?.println(`Welcome, player ${playerAddress}.`); // TODO: Provide own env variable for this feature if (!isProd) { // in development, automatically get some ether from faucet const balance = weiToEth(await ethConnection?.loadBalance(playerAddress)); if (balance === 0) { await requestDevFaucet(playerAddress); } } setStep(TerminalPromptStep.FETCHING_ETH_DATA); } else { setStep(TerminalPromptStep.ASKING_HAS_WHITELIST_KEY); } } catch (e) { console.error(`error connecting to whitelist: ${e}`); terminal.current?.println( 'ERROR: Could not connect to whitelist contract. Please refresh and try again in a few minutes.', TerminalTextStyle.Red ); setStep(TerminalPromptStep.TERMINATED); } }, [ethConnection, isProd, contractAddress] ); const advanceStateFromAskHasWhitelistKey = useCallback( async (terminal: React.MutableRefObject) => { terminal.current?.print('Do you have a whitelist key?', TerminalTextStyle.Text); terminal.current?.println(' (y/n)'); const userInput = await terminal.current?.getInput(); if (userInput === 'y') { setStep(TerminalPromptStep.ASKING_WHITELIST_KEY); } else if (userInput === 'n') { setStep(TerminalPromptStep.ASKING_WAITLIST_EMAIL); } else { terminal.current?.println('Unrecognized input. Please try again.'); await advanceStateFromAskHasWhitelistKey(terminal); } }, [] ); const advanceStateFromAskWhitelistKey = useCallback( async (terminal: React.MutableRefObject) => { const address = ethConnection?.getAddress(); if (!address) throw new Error('not logged in'); terminal.current?.println( 'Please enter your invite key (XXXXXX-XXXXXX-XXXXXX-XXXXXX):', TerminalTextStyle.Sub ); const key = (await terminal.current?.getInput()) || ''; terminal.current?.print('Processing key... (this may take up to 30s)'); terminal.current?.newline(); if (!useZkWhitelist) { let registerConfirmationResponse = {} as RegisterConfirmationResponse; try { registerConfirmationResponse = await callRegisterAndWaitForConfirmation( key, address, terminal ); } catch (e) { registerConfirmationResponse = { canRetry: true, errorMessage: 'There was an error connecting to the whitelist server. Please try again later.', }; } if (!registerConfirmationResponse.txHash) { terminal.current?.println( 'ERROR: ' + registerConfirmationResponse.errorMessage, TerminalTextStyle.Red ); if (registerConfirmationResponse.canRetry) { terminal.current?.println('Press any key to try again.'); await terminal.current?.getInput(); advanceStateFromAskWhitelistKey(terminal); } else { setStep(TerminalPromptStep.ASKING_WAITLIST_EMAIL); } } else { terminal.current?.print('Successfully joined game. ', TerminalTextStyle.Green); terminal.current?.print(`Welcome, player `); terminal.current?.println(address, TerminalTextStyle.Text); terminal.current?.print('Sent player $0.15 :) ', TerminalTextStyle.Blue); terminal.current?.printLink( '(View Transaction)', () => { window.open(`${BLOCK_EXPLORER_URL}/${registerConfirmationResponse.txHash}`); }, TerminalTextStyle.Blue ); terminal.current?.newline(); setStep(TerminalPromptStep.ASKING_PLAYER_EMAIL); } } else { if (!ethConnection) throw new Error('no eth connection'); const contractsAPI = await makeContractsAPI({ connection: ethConnection, contractAddress }); const keyBigInt = bigIntFromKey(key); const snarkArgs = await getWhitelistArgs(keyBigInt, address, terminal); try { const ukReceipt = await contractsAPI.contract.useKey( snarkArgs[ZKArgIdx.PROOF_A], snarkArgs[ZKArgIdx.PROOF_B], snarkArgs[ZKArgIdx.PROOF_C], [...snarkArgs[ZKArgIdx.DATA]] ); await ukReceipt.wait(); terminal.current?.print('Successfully joined game. ', TerminalTextStyle.Green); terminal.current?.print(`Welcome, player `); terminal.current?.println(address, TerminalTextStyle.Text); terminal.current?.print('Sent player $0.15 :) ', TerminalTextStyle.Blue); terminal.current?.printLink( '(View Transaction)', () => { window.open(`${BLOCK_EXPLORER_URL}/${ukReceipt.hash}`); }, TerminalTextStyle.Blue ); terminal.current?.newline(); setStep(TerminalPromptStep.ASKING_PLAYER_EMAIL); } catch (e) { const error = e.error; if (error instanceof Error) { const invalidKey = error.message.includes('invalid key'); if (invalidKey) { terminal.current?.println(`ERROR: Key ${key} is not valid.`, TerminalTextStyle.Red); setStep(TerminalPromptStep.ASKING_WAITLIST_EMAIL); } else { terminal.current?.println(`ERROR: Something went wrong.`, TerminalTextStyle.Red); terminal.current?.println('Press any key to try again.'); await terminal.current?.getInput(); advanceStateFromAskWhitelistKey(terminal); } } console.error('Error whitelisting.'); } } }, [ethConnection, contractAddress, useZkWhitelist] ); const advanceStateFromAskWaitlistEmail = useCallback( async (terminal: React.MutableRefObject) => { terminal.current?.println( 'Enter your email address to sign up for the whitelist.', TerminalTextStyle.Text ); const email = (await terminal.current?.getInput()) || ''; terminal.current?.print('Response pending... '); const response = await submitInterestedEmail(email); if (response === EmailResponse.Success) { terminal.current?.println('Email successfully recorded. ', TerminalTextStyle.Green); terminal.current?.println( 'Keep an eye out for updates and invite keys in the next few weeks. Press ENTER to return to the homepage.', TerminalTextStyle.Sub ); setStep(TerminalPromptStep.TERMINATED); (await await terminal.current?.getInput()) || ''; history.push('/'); } else if (response === EmailResponse.Invalid) { terminal.current?.println('Email invalid. Please try again.', TerminalTextStyle.Red); } else { terminal.current?.print('ERROR: Server error. ', TerminalTextStyle.Red); terminal.current?.print('Press ENTER to return to homepage.', TerminalTextStyle.Sub); (await await terminal.current?.getInput()) || ''; setStep(TerminalPromptStep.TERMINATED); history.push('/'); } }, [history] ); const advanceStateFromAskPlayerEmail = useCallback( async (terminal: React.MutableRefObject) => { const address = ethConnection?.getAddress(); if (!address) throw new Error('not logged in'); terminal.current?.print('Enter your email address. ', TerminalTextStyle.Text); terminal.current?.println("We'll use this email address to notify you if you win a prize."); const email = (await terminal.current?.getInput()) || ''; const response = await submitPlayerEmail(await ethConnection?.signMessageObject({ email })); if (response === EmailResponse.Success) { terminal.current?.println('Email successfully recorded.'); setStep(TerminalPromptStep.FETCHING_ETH_DATA); } else if (response === EmailResponse.Invalid) { terminal.current?.println('Email invalid.', TerminalTextStyle.Red); advanceStateFromAskPlayerEmail(terminal); } else { terminal.current?.println('Error recording email.', TerminalTextStyle.Red); setStep(TerminalPromptStep.FETCHING_ETH_DATA); } }, [ethConnection] ); const advanceStateFromFetchingEthData = useCallback( async (terminal: React.MutableRefObject) => { let newGameManager: GameManager; try { if (!ethConnection) throw new Error('no eth connection'); newGameManager = await GameManager.create({ connection: ethConnection, terminal, contractAddress, }); } catch (e) { console.error(e); setStep(TerminalPromptStep.ERROR); terminal.current?.print( 'Network under heavy load. Please refresh the page, and check ', TerminalTextStyle.Red ); terminal.current?.printLink( 'https://blockscout.com/poa/xdai/', () => { window.open('https://blockscout.com/poa/xdai/'); }, TerminalTextStyle.Red ); terminal.current?.println(''); return; } setGameManager(newGameManager); window.df = newGameManager; const newGameUIManager = await GameUIManager.create(newGameManager, terminal); window.ui = newGameUIManager; terminal.current?.newline(); terminal.current?.println('Connected to Dark Forest Contract'); gameUIManagerRef.current = newGameUIManager; if (!newGameManager.hasJoinedGame()) { setStep(TerminalPromptStep.NO_HOME_PLANET); } else { const browserHasData = !!newGameManager.getHomeCoords(); if (!browserHasData) { terminal.current?.println( 'ERROR: Home coords not found on this browser.', TerminalTextStyle.Red ); setStep(TerminalPromptStep.ASK_ADD_ACCOUNT); return; } terminal.current?.println('Validated Local Data...'); setStep(TerminalPromptStep.ALL_CHECKS_PASS); } }, [ethConnection, contractAddress] ); const advanceStateFromAskAddAccount = useCallback( async (terminal: React.MutableRefObject) => { terminal.current?.println('Import account home coordinates? (y/n)', TerminalTextStyle.Text); terminal.current?.println( "If you're importing an account, make sure you know what you're doing." ); const userInput = await terminal.current?.getInput(); if (userInput === 'y') { setStep(TerminalPromptStep.ADD_ACCOUNT); } else if (userInput === 'n') { terminal.current?.println('Try using a different account and reload.'); setStep(TerminalPromptStep.TERMINATED); } else { terminal.current?.println('Unrecognized input. Please try again.'); await advanceStateFromAskAddAccount(terminal); } }, [] ); const advanceStateFromAddAccount = useCallback( async (terminal: React.MutableRefObject) => { const gameUIManager = gameUIManagerRef.current; if (gameUIManager) { try { terminal.current?.println('x: ', TerminalTextStyle.Blue); const x = parseInt((await terminal.current?.getInput()) || ''); terminal.current?.println('y: ', TerminalTextStyle.Blue); const y = parseInt((await terminal.current?.getInput()) || ''); if ( Number.isNaN(x) || Number.isNaN(y) || Math.abs(x) > 2 ** 32 || Math.abs(y) > 2 ** 32 ) { throw 'Invalid home coordinates.'; } if (await gameUIManager.addAccount({ x, y })) { terminal.current?.println('Successfully added account.'); terminal.current?.println('Initializing game...'); setStep(TerminalPromptStep.ALL_CHECKS_PASS); } else { throw 'Invalid home coordinates.'; } } catch (e) { terminal.current?.println(`ERROR: ${e}`, TerminalTextStyle.Red); terminal.current?.println('Please try again.'); } } else { terminal.current?.println('ERROR: Game UI Manager not found. Terminating session.'); setStep(TerminalPromptStep.TERMINATED); } }, [] ); const advanceStateFromNoHomePlanet = useCallback( async (terminal: React.MutableRefObject) => { terminal.current?.println('Welcome to DARK FOREST.'); const gameUIManager = gameUIManagerRef.current; if (!gameUIManager) { terminal.current?.println('ERROR: Game UI Manager not found. Terminating session.'); setStep(TerminalPromptStep.TERMINATED); return; } if (Date.now() / 1000 > gameUIManager.getEndTimeSeconds()) { terminal.current?.println('ERROR: This game has ended. Terminating session.'); setStep(TerminalPromptStep.TERMINATED); return; } terminal.current?.newline(); terminal.current?.println('We collect a minimal set of statistics such as SNARK proving'); terminal.current?.println('times and average transaction times across browsers, to help '); terminal.current?.println('us optimize performance and fix bugs. You can opt out of this'); terminal.current?.println('in the Settings pane.'); terminal.current?.println(''); terminal.current?.newline(); terminal.current?.println('Press ENTER to find a home planet. This may take up to 120s.'); terminal.current?.println('This will consume a lot of CPU.'); await terminal.current?.getInput(); gameUIManager.getGameManager().on(GameManagerEvent.InitializedPlayer, () => { setTimeout(() => { terminal.current?.println('Initializing game...'); setStep(TerminalPromptStep.ALL_CHECKS_PASS); }); }); gameUIManager .joinGame(async (e) => { console.error(e); terminal.current?.println('Error Joining Game:'); terminal.current?.println(''); terminal.current?.println(e.message, TerminalTextStyle.Red); terminal.current?.println(''); terminal.current?.println('Press Enter to Try Again:'); await terminal.current?.getInput(); return true; }) .catch((error: Error) => { terminal.current?.println( `[ERROR] An error occurred: ${error.toString().slice(0, 10000)}`, TerminalTextStyle.Red ); }); }, [] ); const advanceStateFromAllChecksPass = useCallback( async (terminal: React.MutableRefObject) => { terminal.current?.println(''); terminal.current?.println('Press ENTER to begin'); terminal.current?.println("Press 's' then ENTER to begin in SAFE MODE - plugins disabled"); const input = await terminal.current?.getInput(); if (input === 's') { const gameUIManager = gameUIManagerRef.current; gameUIManager?.getGameManager()?.setSafeMode(true); } setStep(TerminalPromptStep.COMPLETE); setInitRenderState(InitRenderState.COMPLETE); terminal.current?.clear(); terminal.current?.println('Welcome to the Dark Forest.', TerminalTextStyle.Green); terminal.current?.println(''); terminal.current?.println( "This is the Dark Forest interactive JavaScript terminal. Only use this if you know exactly what you're doing." ); terminal.current?.println(''); terminal.current?.println('Try running: df.getAccount()'); terminal.current?.println(''); }, [] ); const advanceStateFromComplete = useCallback( async (terminal: React.MutableRefObject) => { const input = (await terminal.current?.getInput()) || ''; let res = ''; try { // indrect eval call: http://perfectionkills.com/global-eval-what-are-the-options/ res = (1, eval)(input); if (res !== undefined) { terminal.current?.println(res.toString(), TerminalTextStyle.Text); } } catch (e) { res = e.message; terminal.current?.println(`ERROR: ${res}`, TerminalTextStyle.Red); } advanceStateFromComplete(terminal); }, [] ); const advanceStateFromError = useCallback(async () => { await neverResolves(); }, []); const advanceState = useCallback( async (terminal: React.MutableRefObject) => { if (step === TerminalPromptStep.NONE && ethConnection) { await advanceStateFromNone(terminal); } else if (step === TerminalPromptStep.COMPATIBILITY_CHECKS_PASSED) { await advanceStateFromCompatibilityPassed(terminal); } else if (step === TerminalPromptStep.DISPLAY_ACCOUNTS) { await advanceStateFromDisplayAccounts(terminal); } else if (step === TerminalPromptStep.GENERATE_ACCOUNT) { await advanceStateFromGenerateAccount(terminal); } else if (step === TerminalPromptStep.IMPORT_ACCOUNT) { await advanceStateFromImportAccount(terminal); } else if (step === TerminalPromptStep.ACCOUNT_SET) { await advanceStateFromAccountSet(terminal); } else if (step === TerminalPromptStep.ASKING_HAS_WHITELIST_KEY) { await advanceStateFromAskHasWhitelistKey(terminal); } else if (step === TerminalPromptStep.ASKING_WHITELIST_KEY) { await advanceStateFromAskWhitelistKey(terminal); } else if (step === TerminalPromptStep.ASKING_WAITLIST_EMAIL) { await advanceStateFromAskWaitlistEmail(terminal); } else if (step === TerminalPromptStep.ASKING_PLAYER_EMAIL) { await advanceStateFromAskPlayerEmail(terminal); } else if (step === TerminalPromptStep.FETCHING_ETH_DATA) { await advanceStateFromFetchingEthData(terminal); } else if (step === TerminalPromptStep.ASK_ADD_ACCOUNT) { await advanceStateFromAskAddAccount(terminal); } else if (step === TerminalPromptStep.ADD_ACCOUNT) { await advanceStateFromAddAccount(terminal); } else if (step === TerminalPromptStep.NO_HOME_PLANET) { await advanceStateFromNoHomePlanet(terminal); } else if (step === TerminalPromptStep.ALL_CHECKS_PASS) { await advanceStateFromAllChecksPass(terminal); } else if (step === TerminalPromptStep.COMPLETE) { await advanceStateFromComplete(terminal); } else if (step === TerminalPromptStep.ERROR) { await advanceStateFromError(); } }, [ step, advanceStateFromAccountSet, advanceStateFromAddAccount, advanceStateFromAllChecksPass, advanceStateFromAskAddAccount, advanceStateFromAskHasWhitelistKey, advanceStateFromAskPlayerEmail, advanceStateFromAskWaitlistEmail, advanceStateFromAskWhitelistKey, advanceStateFromCompatibilityPassed, advanceStateFromComplete, advanceStateFromDisplayAccounts, advanceStateFromError, advanceStateFromFetchingEthData, advanceStateFromGenerateAccount, advanceStateFromImportAccount, advanceStateFromNoHomePlanet, advanceStateFromNone, ethConnection, ] ); useEffect(() => { const uiEmitter = UIEmitter.getInstance(); uiEmitter.emit(UIEmitterEvent.UIChange); }, [initRenderState]); useEffect(() => { const gameUiManager = gameUIManagerRef.current; if (!terminalVisible && gameUiManager) { const tutorialManager = TutorialManager.getInstance(gameUiManager); tutorialManager.acceptInput(TutorialState.Terminal); } }, [terminalVisible]); useEffect(() => { if (terminalHandle.current && topLevelContainer.current) { advanceState(terminalHandle); } }, [terminalHandle, topLevelContainer, advanceState]); return ( {gameUIManagerRef.current && topLevelContainer.current && gameManager && ( )}
); } ================================================ FILE: src/Frontend/Pages/GifMaker.tsx ================================================ import { ArtifactFileColor } from '@darkforest_eth/gamelogic'; import React, { useEffect, useRef, useState } from 'react'; import styled from 'styled-components'; import { GifRenderer } from '../Renderers/GifRenderer'; const IS_THUMB = true; const GIF_DIM = IS_THUMB ? 90 : 350; export const GIF_ARTIFACT_COLOR = ArtifactFileColor.APP_BACKGROUND; const StyledGifMaker = styled.div` overflow-x: scroll; width: max-content; input { color: black; } img { margin-right: 4px; } `; /** * Entrypoint for gif and sprite generation, accessed via `yarn run gifs`. * Wait a second or so for the textures to get loaded, then click the buttons to download files as a zip. * gifs are saved as 60fps webm, and can take a while - open the console to see progress (logged verbosely) */ export function GifMaker() { const canvasRef = useRef(null); const [renderer, setRenderer] = useState(null); useEffect(() => { if (!canvasRef.current) return; setRenderer(new GifRenderer(canvasRef.current, GIF_DIM, IS_THUMB)); }, [canvasRef]); useEffect(() => { const script = document.createElement('script'); script.src = '/public/CCapture.all.min.js'; script.async = true; document.body.appendChild(script); }, []); return ( ); } ================================================ FILE: src/Frontend/Pages/LandingPage.tsx ================================================ import { CONTRACT_ADDRESS } from '@darkforest_eth/contracts'; import { address } from '@darkforest_eth/serde'; import React from 'react'; import { useHistory } from 'react-router-dom'; import styled from 'styled-components'; import { Btn } from '../Components/Btn'; import { EmSpacer, Link, Spacer, Title } from '../Components/CoreUI'; import { EmailCTA, EmailCTAMode } from '../Components/Email'; import { Modal } from '../Components/Modal'; import { HideSmall, Text, White } from '../Components/Text'; import dfstyles from '../Styles/dfstyles'; import { LandingPageRoundArt } from '../Views/LandingPageRoundArt'; import { LeadboardDisplay } from '../Views/Leaderboard'; export const enum LandingPageZIndex { Background = 0, Canvas = 1, BasePage = 2, } const links = { twitter: 'http://twitter.com/darkforest_eth', email: 'mailto:ivan@0xparc.org', blog: 'https://blog.zkga.me/', discord: 'https://discord.gg/2u2TN6v8r6', github: 'https://github.com/darkforest-eth', wiki: 'https://dfwiki.net/wiki/Main_Page', plugins: 'https://plugins.zkga.me/', }; const defaultAddress = address(CONTRACT_ADDRESS); const ButtonWrapper = styled.div` display: flex; justify-content: center; gap: 8px; flex-direction: row; @media only screen and (max-device-width: 1000px) { grid-template-columns: auto; flex-direction: column; } --df-button-color: ${dfstyles.colors.dfgreen}; --df-button-border: 1px solid ${dfstyles.colors.dfgreen}; --df-button-hover-background: ${dfstyles.colors.dfgreen}; --df-button-hover-border: 1px solid ${dfstyles.colors.dfgreen}; `; export default function LandingPage() { const history = useHistory(); return ( <>
email blog plugins wiki

Dark Forest zkSNARK space warfare
Round 5: The Junk Wars

history.push(`/lobby/${defaultAddress}`)}> Create Lobby history.push(`/play/${defaultAddress}`)}> Enter Round 5 history.push(`/events`)}> Events
Ways to get Involved Space Masters
v0.1 02/22/2020 Dylan Field v0.2 06/24/2020 Nate Foss v0.3 08/07/2020 @hideandcleanse v0.4 10/02/2020 Jacob Rosenthal v0.5 12/25/2020 0xb05d9542... v0.6 round 1 05/22/2021 Ansgar Dietrichs v0.6 round 2 07/07/2021 @orden_gg v0.6 round 3 08/22/2021 @dropswap_gg v0.6 round 4 10/01/2021 @orden_gg v0.6 round 5 02/18/2022 @d_fdao {' + '} @orden_gg
); } const PrettyOverlayGradient = styled.div` width: 100vw; height: 100vh; background: linear-gradient(to left top, rgba(74, 74, 74, 0.628), rgba(60, 1, 255, 0.2)) fixed; background-position: 50%, 50%; display: inline-block; position: fixed; top: 0; left: 0; z-index: -1; `; const Header = styled.div` text-align: center; `; const EmailWrapper = styled.div` display: flex; flex-direction: row; `; const TRow = styled.tr` & td:first-child { color: ${dfstyles.colors.subtext}; } & td:nth-child(2) { padding-left: 12pt; } & td:nth-child(3) { text-align: right; padding-left: 16pt; } `; const MainContentContainer = styled.div` max-width: 100%; display: flex; flex-direction: column; align-items: center; justify-content: space-between; `; const Page = styled.div` position: absolute; width: 100vw; max-width: 100vw; height: 100%; color: white; font-size: ${dfstyles.fontSize}; display: flex; flex-direction: column; align-items: center; z-index: ${LandingPageZIndex.BasePage}; `; const HallOfFameTitle = styled.div` color: ${dfstyles.colors.subtext}; display: inline-block; border-bottom: 1px solid ${dfstyles.colors.subtext}; line-height: 1em; `; export const LinkContainer = styled.div` display: flex; justify-content: center; align-items: center; a { margin: 0 6pt; transition: color 0.2s; display: flex; justify-content: center; align-items: center; &:hover { cursor: pointer; &.link-twitter { color: ${dfstyles.colors.icons.twitter}; } &.link-github { color: ${dfstyles.colors.icons.github}; } &.link-discord { color: ${dfstyles.colors.icons.discord}; } &.link-blog { color: ${dfstyles.colors.icons.blog}; } &.link-email { color: ${dfstyles.colors.icons.email}; } } } `; function Hiring() { return ( Dark Forest is Hiring!
We are looking for experienced full stack and solidity developers to join our team! If you like what you see,{' '} consider applying . If you know someone who you think would be a great fit for our team,{' '} please refer them here .

Learn more about the role{' '} here .
); } const HideOnMobile = styled.div` @media only screen and (max-device-width: 1000px) { display: none; } `; const OnlyMobile = styled.div` @media only screen and (min-device-width: 1000px) { display: none; } `; const Involved = styled.div` width: 100%; padding-left: 16px; padding-right: 16px; display: grid; grid-template-columns: auto auto; gap: 10px; grid-auto-rows: minmax(100px, auto); @media only screen and (max-device-width: 1000px) { grid-template-columns: auto; } `; const InvolvedItem = styled.a` height: 150px; display: inline-block; margin: 4px; padding: 4px 8px; background-color: ${dfstyles.colors.backgroundlighter}; background-size: cover; background-position: 50% 50%; background-repeat: no-repeat; cursor: pointer; transition: transform 200ms; &:hover { transform: scale(1.03); } &:hover:active { transform: scale(1.05); } `; const HallOfFame = styled.div` @media only screen and (max-device-width: 1000px) { font-size: 70%; } `; ================================================ FILE: src/Frontend/Pages/LoadingPage.tsx ================================================ import * as React from 'react'; const styles = { container: { height: '100%', width: '100%', display: 'flex', alignItems: 'center', justifyContent: 'center', }, }; export default function LoadingPage() { return
Loading... (this may take a few seconds)
; } ================================================ FILE: src/Frontend/Pages/LobbyLandingPage.tsx ================================================ import { EthConnection, ThrottledConcurrentQueue, weiToEth } from '@darkforest_eth/network'; import { address } from '@darkforest_eth/serde'; import { EthAddress } from '@darkforest_eth/types'; import { utils, Wallet } from 'ethers'; import React, { useEffect, useRef, useState } from 'react'; import { addAccount, getAccounts } from '../../Backend/Network/AccountManager'; import { getEthConnection } from '../../Backend/Network/Blockchain'; import { InitRenderState, TerminalWrapper } from '../Components/GameLandingPageComponents'; import { MythicLabelText } from '../Components/Labels/MythicLabel'; import { TextPreview } from '../Components/TextPreview'; import { TerminalTextStyle } from '../Utils/TerminalTypes'; import { DarkForestTips } from '../Views/DarkForestTips'; import { Terminal, TerminalHandle } from '../Views/Terminal'; class LobbyPageTerminal { private ethConnection: EthConnection; private terminal: TerminalHandle; private accountSet: (account: EthAddress) => void; private balancesEth: number[]; public constructor( ethConnection: EthConnection, terminal: TerminalHandle, accountSet: (account: EthAddress) => void ) { this.ethConnection = ethConnection; this.terminal = terminal; this.accountSet = accountSet; } private async loadBalances(addresses: EthAddress[]) { const queue = new ThrottledConcurrentQueue({ invocationIntervalMs: 1000, maxInvocationsPerIntervalMs: 25, }); const balances = await Promise.all( addresses.map((address) => queue.add(() => this.ethConnection.loadBalance(address))) ); this.balancesEth = balances.map(weiToEth); } public async chooseAccount() { this.terminal.printElement(); this.terminal.newline(); this.terminal.newline(); this.terminal.printElement(); this.terminal.newline(); const accounts = getAccounts(); this.terminal.println(`Found ${accounts.length} accounts on this device. Loading balances...`); this.terminal.newline(); try { await this.loadBalances(accounts.map((a) => a.address)); } catch (e) { console.log(e); this.terminal.println( `Error loading balances. Reload the page to try again.`, TerminalTextStyle.Red ); return; } this.terminal.println(`Log in to create a lobby. We recommend using an account`); this.terminal.println(`which owns at least 0.25 xDAI.`); this.terminal.newline(); if (accounts.length > 0) { this.terminal.print('(a) ', TerminalTextStyle.Sub); this.terminal.println('Login with existing account.'); } this.terminal.print('(n) ', TerminalTextStyle.Sub); this.terminal.println(`Generate new burner wallet account.`); this.terminal.print('(i) ', TerminalTextStyle.Sub); this.terminal.println(`Import private key.`); this.terminal.println(``); this.terminal.println(`Select an option:`, TerminalTextStyle.Text); const userInput = await this.terminal.getInput(); if (userInput === 'a' && accounts.length > 0) { this.displayAccounts(); } else if (userInput === 'n') { this.generateAccount(); } else if (userInput === 'i') { this.importAccount(); } else { this.terminal.println('Unrecognized input. Please try again.', TerminalTextStyle.Red); this.terminal.println(''); await this.chooseAccount(); } } private async displayAccounts() { this.terminal.println(``); const accounts = getAccounts(); for (let i = 0; i < accounts.length; i += 1) { this.terminal.print(`(${i + 1}): `, TerminalTextStyle.Sub); this.terminal.print(`${accounts[i].address} `); if (this.balancesEth[i] < 0.25) { this.terminal.println(this.balancesEth[i].toFixed(2) + ' xDAI', TerminalTextStyle.Red); } else { this.terminal.println(this.balancesEth[i].toFixed(2) + ' xDAI', TerminalTextStyle.Green); } } this.terminal.println(``); this.terminal.println(`Select an account:`, TerminalTextStyle.Text); const selection = +((await this.terminal.getInput()) || ''); if (isNaN(selection) || selection > accounts.length) { this.terminal.println('Unrecognized input. Please try again.', TerminalTextStyle.Red); await this.displayAccounts(); } else if (this.balancesEth[selection - 1] < 0.25) { this.terminal.println('Not enough xDAI. Select another account.', TerminalTextStyle.Red); await this.displayAccounts(); } else { const account = accounts[selection - 1]; try { await this.ethConnection.setAccount(account.privateKey); this.accountSet(account.address); } catch (e) { this.terminal.println( 'An unknown error occurred. please try again.', TerminalTextStyle.Red ); this.terminal.println(''); this.displayAccounts(); } } } private async generateAccount() { const newWallet = Wallet.createRandom(); const newSKey = newWallet.privateKey; const newAddr = address(newWallet.address); try { addAccount(newSKey); this.ethConnection.setAccount(newSKey); this.terminal.println(``); this.terminal.print(`Created new burner wallet with address `); this.terminal.printElement(); this.terminal.println(``); this.terminal.println(''); this.terminal.println( 'Note: Burner wallets are stored in local storage.', TerminalTextStyle.Text ); this.terminal.println('They are relatively insecure and you should avoid '); this.terminal.println('storing substantial funds in them.'); this.terminal.println(''); this.terminal.println('Also, clearing browser local storage/cache will render your'); this.terminal.println('burner wallets inaccessible, unless you export your private keys.'); this.terminal.println(''); this.terminal.println('Press any key to continue:', TerminalTextStyle.Text); await this.terminal.getInput(); this.accountSet(newAddr); } catch (e) { console.log(e); this.terminal.println('An unknown error occurred. please try again.', TerminalTextStyle.Red); } } private async importAccount() { this.terminal.println( 'Enter the 0x-prefixed private key of the account you wish to import', TerminalTextStyle.Text ); this.terminal.println( "NOTE: THIS WILL STORE THE PRIVATE KEY IN YOUR BROWSER'S LOCAL STORAGE", TerminalTextStyle.Text ); this.terminal.println( 'Local storage is relatively insecure. We recommend only importing accounts with zero-to-no funds.' ); const newSKey = (await this.terminal.getInput()) || ''; try { const newAddr = address(utils.computeAddress(newSKey)); addAccount(newSKey); this.ethConnection.setAccount(newSKey); this.terminal.println(`Imported account with address ${newAddr}.`); this.accountSet(newAddr); } catch (e) { this.terminal.println('An unknown error occurred. please try again.', TerminalTextStyle.Red); this.terminal.println(''); this.importAccount(); } } } export function LobbyLandingPage({ onReady }: { onReady: (connection: EthConnection) => void }) { const terminal = useRef(); const [connection, setConnection] = useState(); const [controller, setController] = useState(); useEffect(() => { getEthConnection() .then((connection) => setConnection(connection)) .catch((e) => { alert('error connecting to blockchain'); console.log(e); }); }, []); useEffect(() => { if (!controller && connection && terminal.current) { const newController = new LobbyPageTerminal( connection, terminal.current, (account: EthAddress) => { if (connection) { terminal.current?.println(`Creating lobby with account: ${account}`); onReady(connection); } else { alert('Unable to make a connection to blockchain'); } } ); newController.chooseAccount(); setController(newController); } }, [terminal, connection, controller, onReady]); return ( ); } const lobbyTips = [ 'A lobby is a Dark Forest universe which is under the control of the account that created it.', 'You can customize most aspects of Dark Forest when you create a lobby.', 'Mirror the X or Y space type for credibly neutral maps.', 'Fixed world radius can be used for a 1v1 battle.', 'Try increasing game speed for a quick round.', 'Use the Admin Controls plugin for god-mode control over your lobby.', 'You can spawn in any space type by adjusting the player spawn perlin range', 'Disable ZK to make mining the unverse super fast. WARNING: insecure.', "Don't like Space Junk? Disable it!", "Don't like Capture Zones? Disable them!", 'Change the Planet Hash Key to change where planets are. Think of it as the seed for planet generation.', 'Change the Space Type Key to vary the space type zones in your lobby.', // TODO: link to the blog post // TODO: link to Jordan's video ]; ================================================ FILE: src/Frontend/Pages/NotFoundPage.tsx ================================================ import React from 'react'; import { CadetWormhole } from '../Views/CadetWormhole'; export function NotFoundPage() { return ; } ================================================ FILE: src/Frontend/Pages/ShareArtifact.tsx ================================================ import { Artifact, ArtifactId } from '@darkforest_eth/types'; import React from 'react'; import { RouteComponentProps } from 'react-router-dom'; import ReaderDataStore from '../../Backend/Storage/ReaderDataStore'; import { Share } from '../Views/Share'; export function ShareArtifact({ match }: RouteComponentProps<{ artifactId: ArtifactId }>) { function load(dataStore: ReaderDataStore) { return dataStore.loadArtifactFromContract(match.params.artifactId); } return ( {(artifact: Artifact | undefined, loading: boolean, error: Error | undefined) => { if (error) { return 'error'; } if (loading) { return 'loading'; } return JSON.stringify(artifact); }} ); } ================================================ FILE: src/Frontend/Pages/SharePlanet.tsx ================================================ import { isLocatable } from '@darkforest_eth/gamelogic'; import { getPlanetBlurb, getPlanetName, getPlanetTagline } from '@darkforest_eth/procedural'; import { LocationId, Planet } from '@darkforest_eth/types'; import React from 'react'; import { RouteComponentProps } from 'react-router-dom'; import styled from 'styled-components'; import ReaderDataStore from '../../Backend/Storage/ReaderDataStore'; import { getPlanetShortHash } from '../../Backend/Utils/Utils'; import { Sub } from '../Components/Text'; import dfstyles from '../Styles/dfstyles'; import { Share } from '../Views/Share'; const PlanetCard = styled.div` width: 36em; margin: 2em auto; font-size: 14pt; z-index: 1000; background: ${dfstyles.colors.background}; border-radius: 3px; border: 1px solid ${dfstyles.colors.text}; & > div { width: 100%; padding: 0.5em; &:nth-child(1) { // header border-bottom: 1px solid ${dfstyles.colors.subtext}; } &:nth-child(2) { // scape height: 350px; } } `; interface SharePlanetData { planet: Planet; biome: number | null; ownerTwitter: string | null; } export function SharePlanet({ match }: RouteComponentProps<{ locationId: LocationId }>) { async function load(dataStore: ReaderDataStore): Promise { const loadedPlanet = await dataStore.loadPlanetFromContract(match.params.locationId); return { planet: loadedPlanet, biome: isLocatable(loadedPlanet) ? loadedPlanet.biome : null, ownerTwitter: dataStore.getTwitter(loadedPlanet?.owner) || null, }; } return ( {(data: SharePlanetData | undefined, loading: boolean, error: Error | undefined) => { if (loading) { return 'loading'; } if (error || !data) { return 'error'; } return (
{getPlanetShortHash(data.planet)} {getPlanetName(data.planet)}

{getPlanetTagline(data.planet)}... {getPlanetBlurb(data.planet)}

{`Owner: ${data.ownerTwitter || data.planet.owner}`}

{`Energy: ${data.planet.energy}`}

{`Biome: ${data.biome || 'unknown'}`}

Find this planet in-game at http://zkga.me to read more!

); }}
); } ================================================ FILE: src/Frontend/Pages/TestArtifactImages.tsx ================================================ import { EMPTY_ARTIFACT_ID } from '@darkforest_eth/constants'; import { ArtifactFileColor, artifactFileName, setForceAncient } from '@darkforest_eth/gamelogic'; import { ArtifactRarity, ArtifactType, Biome } from '@darkforest_eth/types'; import React, { useEffect } from 'react'; import styled from 'styled-components'; import { ARTIFACT_URL } from '../Components/ArtifactImage'; const Container = styled.div` width: fit-content; overflow-x: scroll; `; const Row = styled.div` display: flex; flex-direction: row; justify-content: flex-start; width: fit-content; `; const basicArtifacts = Object.values(ArtifactType).filter( (type) => type >= ArtifactType.Monolith && type <= ArtifactType.Pyramid ); const relicArtifacts = Object.values(ArtifactType).filter( (type) => type >= ArtifactType.Wormhole && type <= ArtifactType.BlackDomain ); const knownTypes = Object.values(ArtifactType).filter((type) => type !== ArtifactType.Unknown); const knownBiomes = Object.values(Biome).filter((biome) => biome !== Biome.UNKNOWN); const knownRarities = Object.values(ArtifactRarity).filter( (rarity) => rarity !== ArtifactRarity.Unknown ); function ArtifactPreviewer({ type, biome, rarity, ancient, thumb, }: { type: ArtifactType; biome: Biome; rarity: ArtifactRarity; ancient?: boolean; thumb: boolean; }) { return ( ); } const THUMB = false; export function TestArtifactImages() { useEffect(() => { setForceAncient(false); }, []); return (

Artifacts

{basicArtifacts.map((type) => (
{knownRarities.map((rarity) => ( {knownBiomes.map((biome, i) => ( ))} ))}
))}

Relics

{relicArtifacts.map((type) => ( {knownRarities.map((rarity, i) => ( ))} ))}

Ancient

{knownTypes.map((type) => ( {knownRarities.map((rarity, i) => ( ))} ))}
); } ================================================ FILE: src/Frontend/Pages/TxConfirmPopup.tsx ================================================ import { weiToGwei } from '@darkforest_eth/network'; import { address } from '@darkforest_eth/serde'; import { Setting } from '@darkforest_eth/types'; import { BigNumber as EthersBN } from 'ethers'; import React, { useState } from 'react'; import { RouteComponentProps } from 'react-router-dom'; import styled, { css, keyframes } from 'styled-components'; import { ONE_DAY } from '../../Backend/Utils/Utils'; import Button from '../Components/Button'; import { Checkbox, DarkForestCheckbox } from '../Components/Input'; import { Row } from '../Components/Row'; import dfstyles from '../Styles/dfstyles'; import { setBooleanSetting } from '../Utils/SettingsHooks'; const StyledTxConfirmPopup = styled.div` width: 100%; height: 100%; position: absolute; z-index: 2; display: flex; flex-direction: column; justify-content: space-between; background: white; color: black; font-family: 'Helvetica', 'Arial', sans-serif; font-size: 12pt; font-weight: 400; .mono { font-family: 'Inconsolata', 'Monaco', monospace; font-size: 11pt; } b { font-weight: 700; } .mtop { margin-top: 1em; } button { flex-grow: 1; padding: 1em; border-radius: 8px; transition: filter 0.1s; &:hover { filter: brightness(1.1); } &:active { filter: brightness(0.9); } &:first-child { margin-right: 0.5em; background: #e3e3e3; border: 2px solid #444; } &:last-child { color: white; background: #00aed9; border: 2px solid #00708b; } } .network { color: ${dfstyles.colors.subtext}; } .section { padding: 0.5em; &:not(:last-of-type) { border-bottom: 1px solid gray; } & > h2 { font-size: 1.5em; font-weight: 300; } } `; const keys = keyframes` from { filter: brightness(1.3); } to { filter: brightness(0.6); } `; const anim = css` animation: ${keys} 1s ${dfstyles.game.styles.animProps}; `; const ConfirmIcon = styled.span` display: inline-block; width: 12px; height: 12px; border-radius: 6px; background: ${dfstyles.colors.dfgreen}; ${anim}; `; export function TxConfirmPopup({ match, }: RouteComponentProps<{ contract: string; addr: string; actionId: string; balance: string; method: string; }>) { const { contract, addr, actionId, balance, method } = match.params; const contractAddress = address(contract); const account = address(addr); const doReject = () => { localStorage.setItem(`tx-approved-${account}-${actionId}`, 'false'); window.close(); }; const [autoApproveChecked, setAutoApprovedChecked] = useState(false); const approve = () => { localStorage.setItem(`tx-approved-${account}-${actionId}`, 'true'); window.close(); }; const setAutoApproveSetting = () => { localStorage.setItem(`tx-approved-${account}-${actionId}`, 'true'); localStorage.setItem(`wallet-enabled-${account}`, (Date.now() + ONE_DAY).toString()); const config = { contractAddress, account, }; setBooleanSetting(config, Setting.AutoApproveNonPurchaseTransactions, true); window.close(); }; const doApprove = () => { if (autoApproveChecked) setAutoApproveSetting(); else approve(); }; const gasFee = EthersBN.from(localStorage.getItem(`${account}-gasFeeGwei`) || ''); const fromPlanet = localStorage.getItem(`${account}-fromPlanet`); const toPlanet = localStorage.getItem(`${account}-toPlanet`); const hatPlanet = localStorage.getItem(`${account}-hatPlanet`); const hatLevel = localStorage.getItem(`${account}-hatLevel`); const hatCost: number = method === 'buyHat' && hatLevel ? 2 ** parseInt(hatLevel) : 0; const txCost: number = hatCost + 0.002 * weiToGwei(gasFee); const upPlanet = localStorage.getItem(`${account}-upPlanet`); const branch = localStorage.getItem(`${account}-branch`); const planetToTransfer = localStorage.getItem(`${account}-transferPlanet`); const transferTo = localStorage.getItem(`${account}-transferOwner`); const findArtifactPlanet = localStorage.getItem(`${account}-findArtifactOnPlanet`); const depositPlanet = localStorage.getItem(`${account}-depositPlanet`); const depositArtifact = localStorage.getItem(`${account}-depositArtifact`); const withdrawPlanet = localStorage.getItem(`${account}-withdrawPlanet`); const withdrawArtifact = localStorage.getItem(`${account}-withdrawArtifact`); const activatePlanet = localStorage.getItem(`${account}-activatePlanet`); const activateArtifact = localStorage.getItem(`${account}-activateArtifact`); const deactivatePlanet = localStorage.getItem(`${account}-deactivatePlanet`); const deactivateArtifact = localStorage.getItem(`${account}-deactivateArtifact`); const withdrawSilverPlanet = localStorage.getItem(`${account}-withdrawSilverPlanet`); const revealPlanet = localStorage.getItem(`${account}-revealLocationId`); return (

Confirm Transaction

Contract Action {method.toUpperCase()} {method === 'revealLocation' && ( Planet ID {revealPlanet} )} {method === 'buyHat' && ( <> On {hatPlanet} HAT Level {hatLevel} ({hatCost} xDAI) )} {method === 'move' && ( <> From {fromPlanet} To {toPlanet} )} {method === 'upgradePlanet' && ( <> On {upPlanet} Branch {branch} )} {method === 'transferPlanet' && ( <> Planet ID {planetToTransfer} Transfer to {transferTo} )} {method === 'findArtifact' && ( Planet ID {findArtifactPlanet} )} {method === 'depositArtifact' && ( <> Planet ID {depositPlanet} Artifact ID {depositArtifact} )} {method === 'withdrawArtifact' && ( <> Planet ID {withdrawPlanet} Artifact ID {withdrawArtifact} )} {method === 'activateArtifact' && ( <> Planet ID {activatePlanet} Artifact ID {activateArtifact} )} {method === 'deactivateArtifact' && ( <> Planet ID {deactivatePlanet} Artifact ID {deactivateArtifact} )} {method === 'withdrawSilver' && ( Planet ID {withdrawSilverPlanet} )}
Gas Fee {weiToGwei(gasFee)} gwei Gas Limit 2000000 Total Transaction Cost {txCost.toFixed(8)} xDAI {method === 'buyHat' && hatLevel && +hatLevel > 6 && ( WARNING: You are buying a very expensive HAT! Check the price and make sure you intend to do this! )} Account Balance {parseFloat(balance).toFixed(8)} xDAI
DF connected to xDAI
) => setAutoApprovedChecked(e.target.checked) } />
); } ================================================ FILE: src/Frontend/Pages/UnsubscribePage.tsx ================================================ import React from 'react'; import { useHistory } from 'react-router-dom'; import styled from 'styled-components'; import Button from '../Components/Button'; import { EmailCTA, EmailCTAMode } from '../Components/Email'; import { BlinkCursor, HideSmall, Invisible, Sub, Text } from '../Components/Text'; import LandingPageCanvas from '../Renderers/LandingPageCanvas'; import dfstyles from '../Styles/dfstyles'; import { LinkContainer } from './LandingPage'; export const enum LandingPageZIndex { Background = 0, Canvas = 1, BasePage = 2, } const styles: { [name: string]: React.CSSProperties; } = { // container stuff wrapper: { width: '100%', height: '100%', }, background: { position: 'absolute', width: '100%', height: '100%', background: dfstyles.colors.background, zIndex: LandingPageZIndex.Background, }, basePage: { position: 'absolute', width: '100%', height: '100%', color: dfstyles.colors.text, fontSize: dfstyles.fontSize, display: 'flex', flexDirection: 'column', justifyContent: 'space-around', alignItems: 'center', zIndex: LandingPageZIndex.BasePage, }, // hall of fame hofTitle: { color: dfstyles.colors.subtext, display: 'inline-block', borderBottom: `1px solid ${dfstyles.colors.subtext}`, lineHeight: '1em', }, }; const links = { twitter: 'http://twitter.com/darkforest_eth', email: 'mailto:ivan@0xparc.org', blog: 'https://blog.zkga.me/', telegram: 'https://t.me/zk_forest', github: 'https://github.com/darkforest-eth', }; // note: prefer styled-components when possible because semantically easier to debug const Header = styled.div` text-align: center; `; const EmailWrapper = styled.div` display: flex; flex-direction: row; `; const Footer = styled.div` display: flex; flex-direction: column; align-items: center; & > div { margin-top: 16pt; } `; const TextLinks = styled.div` & a { &:first-child { margin-left: 0; } margin-left: 7pt; &:after { margin-left: 7pt; content: '-'; color: ${dfstyles.colors.subtext}; } &:last-child:after { display: none; } transition: color 0.2s; &:hover { color: ${dfstyles.colors.dfblue}; } } `; const Title = styled.div` font-size: ${dfstyles.fontH1}; font-family: ${dfstyles.titleFont}; @media (max-width: ${dfstyles.screenSizeS}) { font-size: ${dfstyles.fontH1S}; } position: relative; & h1 { white-space: nowrap; } & h1:first-child { position: absolute; } & h1:last-child { &:before { content: '>'; position: absolute; top: 0; left: -1em; color: #00ff00; } } `; const Fat = styled.span` display: inline-block; transform: scale(1, 1.2); `; export default function UnsubscribePage() { return (
{/* Spacer */}
{/* Title + CTA */}
<h1> dark forest <Fat> <Sub> <BlinkCursor /> </Sub> </Fat> </h1> <h1> <Invisible>dark forest</Invisible> </h1>

zkSNARK space warfare (v0.6)

{/* Email CTA */} {/* Footer */}
{/*
Tutorial About
*/}
); } function _GamesTable() { const history = useHistory(); const games = ['Dark Forest 0.0', 'EF Workshop 2/21', 'ETHGlobal 4/29']; return (
{games.map((name, ind) => (
{name}
))}
); } ================================================ FILE: src/Frontend/Pages/ValhallaPage.tsx ================================================ import React from 'react'; import styled from 'styled-components'; const Container = styled.div` width: 100vw; height: 100vh; display: flex; justify-content: center; align-items: center; flex-direction: column; text-align: center; `; export function ValhallaPage() { window.location.href = 'https://valhalla.zkga.me'; return ; } ================================================ FILE: src/Frontend/Panes/ArtifactCard.tsx ================================================ import { ArtifactId } from '@darkforest_eth/types'; import React from 'react'; import { Padded } from '../Components/CoreUI'; import { useUIManager } from '../Utils/AppHooks'; import { ArtifactDetailsBody } from './ArtifactDetailsPane'; export function ArtifactCard({ artifactId }: { artifactId?: ArtifactId }) { const uiManager = useUIManager(); if (!artifactId) return null; return ( ); } ================================================ FILE: src/Frontend/Panes/ArtifactDetailsPane.tsx ================================================ import { EMPTY_ADDRESS } from '@darkforest_eth/constants'; import { dateMintedAt, hasStatBoost, isActivated, isSpaceShip } from '@darkforest_eth/gamelogic'; import { artifactName, getPlanetName, getPlanetNameHash } from '@darkforest_eth/procedural'; import { Artifact, ArtifactId, ArtifactRarityNames, ArtifactType, EthAddress, LocationId, TooltipName, Upgrade, } from '@darkforest_eth/types'; import _ from 'lodash'; import React from 'react'; import styled from 'styled-components'; import { getUpgradeStat } from '../../Backend/Utils/Utils'; import { ContractConstants } from '../../_types/darkforest/api/ContractsAPITypes'; import { StatIdx } from '../../_types/global/GlobalTypes'; import { ArtifactImage } from '../Components/ArtifactImage'; import { Spacer } from '../Components/CoreUI'; import { StatIcon } from '../Components/Icons'; import { ArtifactRarityLabelAnim, ArtifactTypeText } from '../Components/Labels/ArtifactLabels'; import { ArtifactBiomeLabelAnim } from '../Components/Labels/BiomeLabels'; import { AccountLabel } from '../Components/Labels/Labels'; import { ReadMore } from '../Components/ReadMore'; import { Green, Red, Sub, Text, White } from '../Components/Text'; import { TextPreview } from '../Components/TextPreview'; import { TimeUntil } from '../Components/TimeUntil'; import dfstyles from '../Styles/dfstyles'; import { useArtifact, useUIManager } from '../Utils/AppHooks'; import { ModalHandle } from '../Views/ModalPane'; import { ArtifactActions } from './ManagePlanetArtifacts/ArtifactActions'; import { TooltipTrigger } from './Tooltip'; const StatsContainer = styled.div` flex-grow: 1; `; const ArtifactDetailsHeader = styled.div` min-height: 128px; display: flex; flex-direction: row; & > div::last-child { flex-grow: 1; } .statrow { display: flex; flex-direction: row; justify-content: space-between; align-items: center; & > span:first-child { margin-right: 1.5em; } & > span:last-child { text-align: right; width: 6em; flex-grow: 1; } } `; export function UpgradeStatInfo({ upgrades, stat, }: { upgrades: (Upgrade | undefined)[]; stat: StatIdx; }) { let mult = 100; for (const upgrade of upgrades) { if (upgrade) { mult *= getUpgradeStat(upgrade, stat) / 100; } } const statName = [ TooltipName.Energy, TooltipName.EnergyGrowth, TooltipName.Range, TooltipName.Speed, TooltipName.Defense, ][stat]; return (
{mult > 100 && +{Math.round(mult - 100)}%} {mult === 100 && no effect} {mult < 100 && -{Math.round(100 - mult)}%}
); } const StyledArtifactDetailsBody = styled.div` & > div:first-child p { text-decoration: underline; } & .row { display: flex; flex-direction: row; justify-content: space-between; & > span:first-child { color: ${dfstyles.colors.subtext}; } & > span:last-child { text-align: right; } } & .link { &:hover { cursor: pointer; text-decoration: underline; } } `; const ArtifactName = styled.div` color: ${dfstyles.colors.text}; font-weight: bold; `; const ArtifactNameSubtitle = styled.div` color: ${dfstyles.colors.subtext}; margin-bottom: 8px; `; export function ArtifactDetailsHelpContent() { return (

In this pane, you can see specific information about a particular artifact. You can also initiate a conversation with the artifact! Try talking to your artifacts. Make some new friends (^:

); } export function ArtifactDetailsBody({ artifactId, contractConstants, depositOn, noActions, }: { artifactId: ArtifactId; contractConstants: ContractConstants; modal?: ModalHandle; depositOn?: LocationId; noActions?: boolean; }) { const uiManager = useUIManager(); const artifactWrapper = useArtifact(uiManager, artifactId); const artifact = artifactWrapper.value; if (!artifact) { return null; } const account = (addr: EthAddress) => { const twitter = uiManager?.getTwitter(addr); if (twitter) { return '@' + twitter; } return ; }; const owner = () => { if (!artifact) return ''; return account(artifact.currentOwner); }; const discoverer = () => { if (!artifact) return ''; return account(artifact.discoverer); }; // TODO make this common with playerartifactspane const planetArtifactName = (a: Artifact): string | undefined => { const onPlanet = uiManager?.getArtifactPlanet(a); if (!onPlanet) return undefined; return getPlanetName(onPlanet); }; const planetClicked = (): void => { if (artifact.onPlanetId) uiManager?.setSelectedId(artifact.onPlanetId); }; let readyInStr = undefined; if (artifact.artifactType === ArtifactType.PhotoidCannon && isActivated(artifact)) { readyInStr = ( ); } return ( <>
{isSpaceShip(artifact.artifactType) ? ( <> {artifactName(artifact)} ) : ( <> {artifactName(artifact)} {' '} {' '} )}
{hasStatBoost(artifact.artifactType) && ( {_.range(0, 5).map((val) => ( ))} )} {isSpaceShip(artifact.artifactType) && ( )} {!isSpaceShip(artifact.artifactType) && }
Located On {planetArtifactName(artifact) ? ( {planetArtifactName(artifact)} ) : ( n / a )}
{!isSpaceShip(artifact.artifactType) && ( <>
Minted At {dateMintedAt(artifact)}
Discovered On {getPlanetNameHash(artifact.planetDiscoveredOn)}
Discovered By {discoverer()}
)} {artifact.controller === EMPTY_ADDRESS && (
Owner {owner()}
)}
ID
{artifact.controller !== EMPTY_ADDRESS && (
Controller
)} {readyInStr && (
Ready In {readyInStr}
)} {!noActions && ( )}
); } export function ArtifactDetailsPane({ modal, artifactId, depositOn, }: { modal: ModalHandle; artifactId: ArtifactId; depositOn?: LocationId; }) { const uiManager = useUIManager(); const contractConstants = uiManager.contractConstants; return ( ); } function ArtifactDescription({ artifact, collapsable, }: { artifact: Artifact; collapsable?: boolean; }) { let content; const maxLevelsBlackDomain = [0, 2, 4, 6, 8, 9]; const maxLevelBlackDomain = maxLevelsBlackDomain[artifact.rarity]; const maxLevelsBloomFilter = [0, 2, 4, 6, 8, 9]; const maxLevelBloomFilter = maxLevelsBloomFilter[artifact.rarity]; const wormholeShrinkLevels = [0, 2, 4, 8, 16, 32]; const rarityName = ArtifactRarityNames[artifact.rarity]; const photoidRanges = [0, 2, 2, 2, 2, 2]; const photoidSpeeds = [0, 5, 10, 15, 20, 25]; const genericSpaceshipDescription = <>Can move between planets without sending energy.; switch (artifact.artifactType) { case ArtifactType.BlackDomain: content = ( When activated, permanently disables your planet. It'll still be yours, but you won't be able to do anything with it. It turns completely black too. Just ... gone. Because this one is {rarityName}, it works on planets up to level{' '} {maxLevelBlackDomain}. This artifact is consumed on activation. ); break; case ArtifactType.BloomFilter: content = ( When activated refills your planet's energy and silver to their respective maximum values. How it does this, we do not know. Because this one is {rarityName}, it works on planets up to level {maxLevelBloomFilter}. This artifact is consumed on activation. ); break; case ArtifactType.Wormhole: content = ( When activated, shortens the distance between this planet and another one. All moves between those two planets decay less energy, and complete faster.{' '} Energy sent through your wormhole to a planet you do not control does not arrive. {' '} Because this one is {rarityName}, it shrinks the distance by a factor of{' '} {wormholeShrinkLevels[artifact.rarity]}x. ); break; case ArtifactType.PhotoidCannon: content = ( Ahh, the Photoid Canon. Activate it, wait four hours. Because this one is{' '} {rarityName}, the next move you send will be able to go{' '} {photoidRanges[artifact.rarity]}x further and{' '} {photoidSpeeds[artifact.rarity]}x faster. During the 4 hour waiting period, your planet's defense is temporarily decreased. This artifact is consumed once the canon is fired. ); break; case ArtifactType.PlanetaryShield: content = ( Activate the planetary shield to gain a defense bonus on your planet, at the expense of range and speed. When this artifact is deactivated, it is destroyed and your planet's stats are reverted--so use it wisely! ); break; case ArtifactType.ShipMothership: content = ( Doubles energy regeneration of the planet that it is currently on.{' '} {genericSpaceshipDescription} ); break; case ArtifactType.ShipCrescent: content = ( Activate to convert an un-owned planet whose level is more than 0 into an Asteroid Field.{' '} Can only be used once. {genericSpaceshipDescription} ); break; case ArtifactType.ShipGear: content = ( Allows you to prospect planets, and subsequently find artifacts on them.{' '} {genericSpaceshipDescription} ); break; case ArtifactType.ShipTitan: content = ( Pauses energy and silver regeneration on the planet it's on. {genericSpaceshipDescription} ); break; case ArtifactType.ShipWhale: content = ( Doubles the silver regeneration of the planet that it is currently on.{' '} {genericSpaceshipDescription} ); break; } if (content) { return (
{collapsable ? ( {content} ) : ( content )}
); } return null; } ================================================ FILE: src/Frontend/Panes/ArtifactHoverPane.tsx ================================================ import React from 'react'; import { useHoverArtifactId, useUIManager } from '../Utils/AppHooks'; import { ArtifactCard } from './ArtifactCard'; import { HoverPane } from './HoverPane'; export function ArtifactHoverPane() { const uiManager = useUIManager(); const hoverArtifactId = useHoverArtifactId(uiManager); return ( } /> ); } ================================================ FILE: src/Frontend/Panes/ArtifactsList.tsx ================================================ import { isSpaceShip } from '@darkforest_eth/gamelogic'; import { artifactName, getPlanetName } from '@darkforest_eth/procedural'; import { Artifact, ArtifactTypeNames, LocationId } from '@darkforest_eth/types'; import React from 'react'; import styled from 'styled-components'; import GameUIManager from '../../Backend/GameLogic/GameUIManager'; import { CenterBackgroundSubtext, Truncate } from '../Components/CoreUI'; import { ArtifactRarityLabelAnim } from '../Components/Labels/ArtifactLabels'; import { Sub } from '../Components/Text'; import { useUIManager } from '../Utils/AppHooks'; import { ArtifactLink } from '../Views/ArtifactLink'; import { ModalHandle } from '../Views/ModalPane'; import { PlanetLink } from '../Views/PlanetLink'; import { SortableTable } from '../Views/SortableTable'; import { TabbedView } from '../Views/TabbedView'; const ArtifactsBody = styled.div` min-height: 200px; max-height: 400px; overflow-y: scroll; `; const PlanetName = styled.span` display: block; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 100px; `; const planetArtifactName = (a: Artifact, uiManager: GameUIManager): string | undefined => { const onPlanet = uiManager?.getArtifactPlanet(a); if (!onPlanet) return undefined; return getPlanetName(onPlanet); }; export function ArtifactsList({ modal, artifacts, depositOn, maxRarity, noArtifactsMessage, }: { modal: ModalHandle; artifacts: Artifact[]; depositOn?: LocationId; maxRarity?: number; noArtifactsMessage?: React.ReactElement; }) { const uiManager = useUIManager(); let nonShipArtifacts = artifacts.filter((a) => !isSpaceShip(a.artifactType)); if (maxRarity !== undefined) { nonShipArtifacts = nonShipArtifacts.filter((a) => a.rarity <= maxRarity); } const headers = ['Name', 'Location', 'Type', 'Rarity']; const alignments: Array<'r' | 'c' | 'l'> = ['l', 'r', 'r', 'r']; const columns = [ (artifact: Artifact) => ( {artifactName(artifact)} ), (artifact: Artifact) => { const planetOn = uiManager.getArtifactPlanet(artifact); const planetOnName = planetArtifactName(artifact, uiManager); return ( {planetOnName && planetOn ? ( {planetOnName} ) : ( wallet )} ); }, (artifact: Artifact) => ( {ArtifactTypeNames[artifact.artifactType]} ), (artifact: Artifact) => , ]; const sortFunctions = [ (left: Artifact, right: Artifact) => artifactName(left).localeCompare(artifactName(right)), (left: Artifact, right: Artifact) => planetArtifactName(left, uiManager)?.localeCompare( planetArtifactName(right, uiManager) || '' ) || 0, (left: Artifact, right: Artifact) => ArtifactTypeNames[left.artifactType]?.localeCompare( ArtifactTypeNames[right.artifactType] || '' ) || 0, (left: Artifact, right: Artifact) => left.rarity - right.rarity, ]; if (nonShipArtifacts.length === 0) { return ( {noArtifactsMessage ?? ( <> You Don't Have
Any Artifacts )}
); } return ( ); } export function ShipList({ modal, artifacts, depositOn, noShipsMessage, }: { modal: ModalHandle; artifacts: Artifact[]; depositOn?: LocationId; noShipsMessage?: React.ReactElement; }) { const uiManager = useUIManager(); const headers = ['Name', 'Location', 'Type']; const alignments: Array<'r' | 'c' | 'l'> = ['l', 'r', 'r', 'r']; const shipArtifacts = artifacts.filter((a) => isSpaceShip(a.artifactType)); const columns = [ (artifact: Artifact) => ( {artifactName(artifact)} ), (artifact: Artifact) => { const planetOn = uiManager.getArtifactPlanet(artifact); const planetOnName = planetArtifactName(artifact, uiManager); return ( {planetOnName && planetOn ? ( {planetOnName} ) : ( moving )} ); }, (artifact: Artifact) => ( {ArtifactTypeNames[artifact.artifactType]} ), ]; const sortFunctions = [ (left: Artifact, right: Artifact) => artifactName(left).localeCompare(artifactName(right)), (left: Artifact, right: Artifact) => planetArtifactName(left, uiManager)?.localeCompare( planetArtifactName(right, uiManager) || '' ) || 0, (left: Artifact, right: Artifact) => ArtifactTypeNames[left.artifactType]?.localeCompare( ArtifactTypeNames[right.artifactType] || '' ) || 0, ]; if (shipArtifacts.length === 0) { return ( {noShipsMessage ?? ( <> You Don't Have
Any Ships )}
); } return ( ); } export function AllArtifacts({ modal, artifacts, depositOn, maxRarity, noArtifactsMessage, noShipsMessage, }: { modal: ModalHandle; artifacts: Artifact[]; depositOn?: LocationId; maxRarity?: number; noArtifactsMessage?: React.ReactElement; noShipsMessage?: React.ReactElement; }) { return ( { if (i === 0) { return ( ); } return ( ); }} /> ); } ================================================ FILE: src/Frontend/Panes/BroadcastPane.tsx ================================================ import { isUnconfirmedRevealTx } from '@darkforest_eth/serde'; import { EthAddress, LocationId } from '@darkforest_eth/types'; import React, { useEffect, useState } from 'react'; import styled from 'styled-components'; import { Btn } from '../Components/Btn'; import { CenterBackgroundSubtext, Spacer } from '../Components/CoreUI'; import { LoadingSpinner } from '../Components/LoadingSpinner'; import { Blue, White } from '../Components/Text'; import { formatDuration, TimeUntil } from '../Components/TimeUntil'; import dfstyles from '../Styles/dfstyles'; import { usePlanet, useUIManager } from '../Utils/AppHooks'; import { useEmitterValue } from '../Utils/EmitterHooks'; import { ModalHandle } from '../Views/ModalPane'; const BroadcastWrapper = styled.div` & .row { display: flex; flex-direction: row; justify-content: space-between; & > span { &:first-child { color: ${dfstyles.colors.subtext}; padding-right: 1em; } } } & .message { margin: 1em 0; & p { margin: 0.5em 0; &:last-child { margin-bottom: 1em; } } } `; export function BroadcastPaneHelpContent() { return (
Reveal this planet's location to all other players on-chain! Broadcasting can be a potent offensive tactic! Reveal a powerful enemy's location, and maybe someone else will take care of them for you?
); } export function BroadcastPane({ initialPlanetId, modal: _modal, }: { modal: ModalHandle; initialPlanetId: LocationId | undefined; }) { const uiManager = useUIManager(); const planetId = useEmitterValue(uiManager.selectedPlanetId$, initialPlanetId); const planet = usePlanet(uiManager, planetId).value; const getLoc = () => { if (!planet || !uiManager) return { x: 0, y: 0 }; const loc = uiManager.getLocationOfPlanet(planet.locationId); if (!loc) return { x: 0, y: 0 }; return loc.coords; }; const broadcast = () => { if (!planet || !uiManager) return; const loc = uiManager.getLocationOfPlanet(planet.locationId); if (!loc) return; uiManager.revealLocation(loc.hash); }; const [account, setAccount] = useState(undefined); // consider moving this one to parent const isRevealed = planet?.coordsRevealed; const broadcastCooldownPassed = uiManager.getNextBroadcastAvailableTimestamp() <= Date.now(); const currentlyBroadcastingAnyPlanet = uiManager.isCurrentlyRevealing(); useEffect(() => { if (!uiManager) return; setAccount(uiManager.getAccount()); }, [uiManager]); let revealBtn = undefined; if (isRevealed) { revealBtn = Broadcast Coordinates; } else if (planet?.transactions?.hasTransaction(isUnconfirmedRevealTx)) { revealBtn = ( ); } else if (!broadcastCooldownPassed) { revealBtn = Broadcast Coordinates; } else { revealBtn = ( Broadcast Coordinates ); } const warningsSection = (
{currentlyBroadcastingAnyPlanet && (

INFO: Revealing...

)} {planet?.owner === account && (

INFO: You own this planet! Revealing its location is a dangerous flex.

)} {isRevealed && (

INFO: This planet's location is already revealed, and can't be revealed again!

)} {!broadcastCooldownPassed && (

INFO: You must wait{' '} {' '} to reveal another planet.

)}
); if (planet) { return (
You can broadcast a planet to publically reveal its location on the map. You can only broadcast a planet's location once every{' '} {formatDuration(uiManager.contractConstants.LOCATION_REVEAL_COOLDOWN * 1000)} .
{warningsSection}
Coordinates {`(${getLoc().x}, ${getLoc().y})`}

{revealBtn}

); } else { return ( Select a Planet ); } } ================================================ FILE: src/Frontend/Panes/CoordsPane.tsx ================================================ import { SpaceType, WorldCoords } from '@darkforest_eth/types'; import React, { useState } from 'react'; import styled from 'styled-components'; import GameUIManager from '../../Backend/GameLogic/GameUIManager'; import { useUIManager } from '../Utils/AppHooks'; import UIEmitter, { UIEmitterEvent } from '../Utils/UIEmitter'; class CoordsText extends React.Component< { uiManager: GameUIManager | undefined; }, never > { private coordsRef = React.createRef(); private spacetypeRef = React.createRef(); private uiEmitter = UIEmitter.getInstance(); componentDidMount() { this.uiEmitter.on(UIEmitterEvent.WorldMouseMove, this.update); } componentWillUnmount() { this.uiEmitter.removeListener(UIEmitterEvent.WorldMouseMove, this.update); } update = (coords: WorldCoords) => { this.setCoords(coords); this.setSpacetype(coords); }; setCoords(coords: WorldCoords) { if (this.coordsRef.current) { this.coordsRef.current.innerText = coords ? `(${Math.round(coords.x)}, ${Math.round(coords.y)})` : '(x, y)'; } } setSpacetype(coords: WorldCoords) { let spacetypeText = '???'; if (this.props.uiManager) { const per = this.props.uiManager.getSpaceTypePerlin(coords, false); const spaceType = this.props.uiManager.spaceTypeFromPerlin(per); let suff = ''; if (spaceType === SpaceType.NEBULA) suff = '\u00b0 (NEBULA)'; else if (spaceType === SpaceType.SPACE) suff = '\u00b0 (SPACE)'; else if (spaceType === SpaceType.DEEP_SPACE) suff = '\u00b0 (DEEP SPACE)'; else if (spaceType === SpaceType.DEAD_SPACE) suff = '\u00b0 (DEAD SPACE)'; spacetypeText = `${Math.floor((16 - per) * 16)}${suff}`; } if (this.spacetypeRef.current) { this.spacetypeRef.current.innerText = 'TEMP: ' + spacetypeText; } } render() { return ( <> ); } } const StyledCoordsPane = styled.div` position: absolute; bottom: 0; right: 0; padding: 0.5em; text-align: right; display: flex; flex-direction: column; justify-content: flex-end; width: 16em; height: 4em; `; export function CoordsPane() { const [hovering, setHovering] = useState(false); const [hidden, setHidden] = useState(false); const uiManager = useUIManager(); return ( setHidden((b) => !b)} onMouseEnter={() => setHovering(true)} onMouseLeave={() => setHovering(false)} > {hidden ? ( {hovering ? 'Click to show' : ''} ) : hovering ? ( Click to hide ) : ( )} ); } ================================================ FILE: src/Frontend/Panes/DiagnosticsPane.tsx ================================================ import { RECOMMENDED_MODAL_WIDTH } from '@darkforest_eth/constants'; import { Diagnostics, ModalName, Setting } from '@darkforest_eth/types'; import React, { useEffect, useState } from 'react'; import { Wrapper } from '../../Backend/Utils/Wrapper'; import { EmSpacer, Separator, SpreadApart } from '../Components/CoreUI'; import { DisplayGasPrices } from '../Components/DisplayGasPrices'; import { TextPreview } from '../Components/TextPreview'; import { useUIManager } from '../Utils/AppHooks'; import { BooleanSetting } from '../Utils/SettingsHooks'; import { ModalPane } from '../Views/ModalPane'; import { TabbedView } from '../Views/TabbedView'; export function DiagnosticsPane({ visible, onClose }: { visible: boolean; onClose: () => void }) { const uiManager = useUIManager(); const [currentDiagnostics, setCurrentDiagnostics] = useState( new Wrapper(uiManager.getDiagnostics()) ); useEffect(() => { const interval = setInterval(() => { setCurrentDiagnostics(new Wrapper(uiManager.getDiagnostics())); }, 250); return () => clearInterval(interval); }, [uiManager]); return ( ); } function DiagnosticsTabs({ diagnostics }: { diagnostics: Wrapper }) { return ( { switch (i) { case 0: return ; case 1: return ; } }} /> ); } function RenderingTab({ diagnostics }: { diagnostics: Wrapper }) { const uiManager = useUIManager(); return ( <> fps {Math.floor(diagnostics.value.fps)} visible chunks {diagnostics.value.visibleChunks.toLocaleString()} total chunks {diagnostics.value.totalChunks.toLocaleString()} visible planets {diagnostics.value.visiblePlanets.toLocaleString()} total planets {diagnostics.value.totalPlanets.toLocaleString()} queued chunk writes {diagnostics.value.chunkUpdates.toLocaleString()} viewport {diagnostics.value.width?.toLocaleString()} x {diagnostics.value.height?.toLocaleString()} ); } function NetworkingTab({ diagnostics }: { diagnostics: Wrapper }) { return ( <> rpc url completed calls {diagnostics.value.totalCalls.toLocaleString()} queued calls {diagnostics.value.callsInQueue.toLocaleString()} completed transactions {diagnostics.value.totalTransactions.toLocaleString()} queued transactions {diagnostics.value.transactionsInQueue.toLocaleString()} oracle gas prices ); } ================================================ FILE: src/Frontend/Panes/ExplorePane.tsx ================================================ import { CursorState, ModalManagerEvent, Setting, TooltipName, WorldCoords, } from '@darkforest_eth/types'; import React, { useEffect, useState } from 'react'; import styled from 'styled-components'; import TutorialManager, { TutorialState } from '../../Backend/GameLogic/TutorialManager'; import { MiningPatternType, SpiralPattern, SwissCheesePattern, TowardsCenterPattern, TowardsCenterPatternV2, } from '../../Backend/Miner/MiningPatterns'; import { EmSpacer, SelectFrom } from '../Components/CoreUI'; import { Icon, IconType } from '../Components/Icons'; import { MaybeShortcutButton } from '../Components/MaybeShortcutButton'; import { Coords, Sub } from '../Components/Text'; import dfstyles from '../Styles/dfstyles'; import { useUIManager } from '../Utils/AppHooks'; import { MIN_CHUNK_SIZE } from '../Utils/constants'; import { MultiSelectSetting, useBooleanSetting } from '../Utils/SettingsHooks'; import { TOGGLE_EXPLORE, TOGGLE_TARGETTING } from '../Utils/ShortcutConstants'; import UIEmitter, { UIEmitterEvent } from '../Utils/UIEmitter'; import { TooltipTrigger } from './Tooltip'; const StyledExplorePane = styled.div` background: ${dfstyles.colors.background}; position: absolute; bottom: 0; left: 0; padding: 0.5em; margin: 0.5em; /* border: 1px solid ${dfstyles.colors.subtext}; */ border-radius: ${dfstyles.borderRadius}; `; function Cores() { const uiManager = useUIManager(); const values = ['1', '2', '4', '8', '16', '32']; const labels = values.map((value) => value + ' core' + (value === '1' ? '' : 's')); return ( ); } const Pattern = { [MiningPatternType.Spiral.toString()]: SpiralPattern, [MiningPatternType.SwissCheese.toString()]: SwissCheesePattern, [MiningPatternType.TowardsCenter.toString()]: TowardsCenterPattern, [MiningPatternType.TowardsCenterV2.toString()]: TowardsCenterPatternV2, }; const miningSelectValues = [ MiningPatternType.Spiral.toString(), MiningPatternType.SwissCheese.toString(), MiningPatternType.TowardsCenter.toString(), MiningPatternType.TowardsCenterV2.toString(), ]; const miningSelectLabels = ['Spiral', 'SwissCheese', 'TowardsCenter', 'TowardsCenterV2']; function HashesPerSec() { const uiManager = useUIManager(); const [hashRate, setHashRate] = useState(0); useEffect(() => { if (!uiManager) return; const updateHashes = () => { setHashRate(uiManager.getHashesPerSec()); }; const intervalId = setInterval(updateHashes, 1000); updateHashes(); return () => { clearInterval(intervalId); }; }, [uiManager]); const getHashes = () => { return Math.floor(hashRate).toLocaleString(); }; return ( <> {getHashes()} #/s ); } export function ExplorePane() { const uiManager = useUIManager(); const modalManager = uiManager.getModalManager(); const uiEmitter = UIEmitter.getInstance(); const [pattern, setPattern] = useState(MiningPatternType.Spiral.toString()); const [mining] = useBooleanSetting(uiManager, Setting.IsMining); const [targetting, setTargetting] = useState(false); const [coords, setCoords] = useState(uiManager.getHomeCoords()); useEffect(() => { const doMouseDown = (worldCoords: WorldCoords) => { if (modalManager.getCursorState() === CursorState.TargetingExplorer) { modalManager.acceptInputForTarget({ x: Math.floor(worldCoords.x), y: Math.floor(worldCoords.y), }); } }; const updateCoords = (worldCoords: WorldCoords) => { const PatternCtor = Pattern[pattern]; const newpattern = new PatternCtor(worldCoords, MIN_CHUNK_SIZE); uiManager?.setMiningPattern(newpattern); setCoords(worldCoords); const tutorialManager = TutorialManager.getInstance(uiManager); tutorialManager.acceptInput(TutorialState.MinerMove); }; const cursorStateChanged = (state: CursorState) => { setTargetting(state === CursorState.TargetingExplorer); }; uiEmitter.on(UIEmitterEvent.WorldMouseDown, doMouseDown); modalManager.on(ModalManagerEvent.MiningCoordsUpdate, updateCoords); uiEmitter.on(ModalManagerEvent.StateChanged, cursorStateChanged); return () => { uiEmitter.removeListener(UIEmitterEvent.WorldMouseDown, doMouseDown); modalManager.removeListener(ModalManagerEvent.MiningCoordsUpdate, updateCoords); uiEmitter.removeListener(ModalManagerEvent.StateChanged, cursorStateChanged); }; }, [uiEmitter, modalManager, uiManager, pattern]); const updatePattern = (pattern: string) => { setPattern(pattern); const PatternCtor = Pattern[pattern]; const newpattern = new PatternCtor(coords, MIN_CHUNK_SIZE); uiManager.setMiningPattern(newpattern); }; const doTarget = () => { uiManager.toggleTargettingExplorer(); }; const doExplore = () => { uiManager.toggleExplore(); }; return ( {/* button which allows player to preposition the center of their miner */} {targetting ? 'Moving...' : 'Move'} {/* button which toggles whether or not the game is mining. this persists between refreshes */} {mining ? 'Pause' : 'Explore!'} {' '} {mining ? : } {mining && ( <> {/* TODO: Make this a Settings thing */} )} ); } ================================================ FILE: src/Frontend/Panes/HatPane.tsx ================================================ import { weiToEth } from '@darkforest_eth/network'; import { getHatSizeName, getPlanetCosmetic } from '@darkforest_eth/procedural'; import { isUnconfirmedBuyHatTx } from '@darkforest_eth/serde'; import { LocationId, Planet } from '@darkforest_eth/types'; import { BigNumber } from 'ethers'; import React from 'react'; import styled from 'styled-components'; import { Btn } from '../Components/Btn'; import { CenterBackgroundSubtext, EmSpacer, Link } from '../Components/CoreUI'; import { Sub } from '../Components/Text'; import { useAccount, usePlanet, useUIManager } from '../Utils/AppHooks'; import { useEmitterValue } from '../Utils/EmitterHooks'; import { ModalHandle } from '../Views/ModalPane'; const StyledHatPane = styled.div` & > div { display: flex; flex-direction: row; justify-content: space-between; &:last-child > span { margin-top: 1em; text-align: center; flex-grow: 1; } &.margin-top { margin-top: 0.5em; } } `; const getHatCostEth = (planet: Planet) => { return 2 ** planet.hatLevel; }; export function HatPane({ initialPlanetId, modal: _modal, }: { modal: ModalHandle; initialPlanetId?: LocationId; }) { const uiManager = useUIManager(); const account = useAccount(uiManager); const planetId = useEmitterValue(uiManager.selectedPlanetId$, initialPlanetId); const planetWrapper = usePlanet(uiManager, planetId); const planet = planetWrapper.value; const balanceEth = weiToEth( useEmitterValue(uiManager.getEthConnection().myBalance$, BigNumber.from('0')) ); const enabled = (planet: Planet): boolean => !planet.transactions?.hasTransaction(isUnconfirmedBuyHatTx) && planet?.owner === account && balanceEth > getHatCostEth(planet); if (planet && planet.owner === account) { return (
HAT {getPlanetCosmetic(planet).hatType}
HAT Level {getHatSizeName(planet)}
Next Level Cost {getHatCostEth(planet)} USD / {getHatCostEth(planet)} DAI
Current Balance {balanceEth} xDAI
Get More xDai { if (!enabled(planet) || !uiManager || !planet) return; uiManager.buyHat(planet); }} disabled={!enabled(planet)} > {planet && planet.hatLevel > 0 ? 'Upgrade' : 'Buy'} HAT
); } else { return ( Select a Planet
You Own
); } } ================================================ FILE: src/Frontend/Panes/HelpPane.tsx ================================================ import { ArtifactRarity, ModalName, PlanetLevel } from '@darkforest_eth/types'; import React from 'react'; import styled from 'styled-components'; import { EmSpacer, Link, Section, SectionHeader } from '../Components/CoreUI'; import { ArtifactRarityLabel } from '../Components/Labels/ArtifactLabels'; import { Gold, White } from '../Components/Text'; import dfstyles from '../Styles/dfstyles'; import { useUIManager } from '../Utils/AppHooks'; import { ModalPane } from '../Views/ModalPane'; const HelpContent = styled.div` width: 500px; height: 500px; max-height: 500px; max-width: 500px; overflow-y: scroll; text-align: justify; color: ${dfstyles.colors.text}; `; export function HelpPane({ visible, onClose }: { visible: boolean; onClose: () => void }) { const uiManager = useUIManager(); const silverScoreValue = uiManager.getSilverScoreValue(); const artifactPointValues = uiManager.getArtifactPointValues(); const captureZonePointValues = uiManager.getCaptureZonePointValues(); return ( {uiManager.isRoundOver() && (
Round 5 Complete Dark Forest v0.6 Round 5 is now complete! Scores are being compiled and winners will be announced shortly. Also, Artifacts will no longer be mintable. Thanks for playing!
)}
Firstly, Some Links: Official Info and Announcements
Official Twitter
Official Discord Server
Community-Run Wiki

Secondly... welcome to
Dark Forest v0.6 R5: The Junk Wars Dark Forest is a vast universe, obfuscated by zero-knowledge cryptography. Your{' '} explorer (bottom left) explores the universe, searching for{' '} Planets and other players. All planets produce Energy. You can click-drag to move energy from planets you own to new planets to conquer them. Also scattered through the universe are Asteroid Fields, which produce{' '} Silver. Silver can be sent to planets and can be spent on{' '} Upgrades. Some planets contain Artifacts - ERC721 tokens that can be traded with other players. Artifacts can be harvested and deposited onto planets, buffing their stats.
Prizes and ScoringA snapshot of scores will be taken on{' '} February 28, 2022 at 9PM Pacific Time. At that time, the top 63 highest-scoring players will be awarded prizes from a pool 63 prize planets. You can see the current rankings by scrolling down on the landing page of the game. Scoring this round is made up of three parts: finding artifacts using you Gear ship, withdrawing silver from Spacetime Rips, and invading and capturing planets inside of Capture Zones. For more information about capture zones, hover over the 'Capture Zones' sections at the top of the screen. The values for each scoring type are provided below:
Scoring values Each single silver you withdraw increases your score by{' '} {silverScoreValue / 100}. Discovering an artifact increases your score based on its rarity:
:{' '} {artifactPointValues[ArtifactRarity.Common]}
:{' '} {artifactPointValues[ArtifactRarity.Rare]}
:{' '} {artifactPointValues[ArtifactRarity.Epic]}
:{' '} {artifactPointValues[ArtifactRarity.Legendary]}
:{' '} {artifactPointValues[ArtifactRarity.Mythic]} Capturing an invaded planet increases your score based on its level:
Level {PlanetLevel.ZERO}: {captureZonePointValues[PlanetLevel.ZERO]}
Level {PlanetLevel.ONE}: {captureZonePointValues[PlanetLevel.ONE]}
Level {PlanetLevel.TWO}: {captureZonePointValues[PlanetLevel.TWO]}
Level {PlanetLevel.THREE}: {captureZonePointValues[PlanetLevel.THREE]}
Level {PlanetLevel.FOUR}: {captureZonePointValues[PlanetLevel.FOUR]}
Level {PlanetLevel.FIVE}: {captureZonePointValues[PlanetLevel.FIVE]}
Level {PlanetLevel.SIX}: {captureZonePointValues[PlanetLevel.SIX]}
Level {PlanetLevel.SEVEN}: {captureZonePointValues[PlanetLevel.SEVEN]}
Level {PlanetLevel.EIGHT}: {captureZonePointValues[PlanetLevel.EIGHT]}
Level {PlanetLevel.NINE}: {captureZonePointValues[PlanetLevel.NINE]}
); } ================================================ FILE: src/Frontend/Panes/HoverPane.tsx ================================================ import React, { useLayoutEffect, useRef } from 'react'; import styled from 'styled-components'; import { snips } from '../Styles/dfstyles'; import { DFZIndex } from '../Utils/constants'; const StyledHoverPane = styled.div` ${snips.absoluteTopLeft} ${snips.defaultBackground} ${snips.roundedBordersWithEdge} width: 350px; `; /** * This is the pane that is rendered when you hover over a planet. */ export function HoverPane({ style, visible, element, }: { style?: React.CSSProperties; visible: boolean; element: React.ReactChild; }) { const paneRef = useRef(null); useLayoutEffect(() => { if (!paneRef.current) return; let leftOffset; let topOffset; const doMouseMove = (e: MouseEvent) => { if (!paneRef.current) return; const width = paneRef.current.offsetWidth; const height = paneRef.current.offsetHeight; if (e.clientX < window.innerWidth / 2) leftOffset = 10; else leftOffset = -10 - width; if (e.clientY < window.innerHeight / 2) topOffset = 10; else topOffset = -10 - height; paneRef.current.style.top = e.clientY + topOffset + 'px'; paneRef.current.style.left = e.clientX + leftOffset + 'px'; }; window.addEventListener('mousemove', doMouseMove); return () => window.removeEventListener('mousemove', doMouseMove); }, [paneRef]); return ( {element} ); } ================================================ FILE: src/Frontend/Panes/HoverPlanetPane.tsx ================================================ import React, { useEffect, useMemo, useState } from 'react'; import { snips } from '../Styles/dfstyles'; import { useHoverPlanet, useSelectedPlanet, useUIManager } from '../Utils/AppHooks'; import UIEmitter, { UIEmitterEvent } from '../Utils/UIEmitter'; import { PlanetCard } from '../Views/PlanetCard'; import { HoverPane } from './HoverPane'; /** * This is the pane that is rendered when you hover over a planet. */ export function HoverPlanetPane() { const uiManager = useUIManager(); const hoverWrapper = useHoverPlanet(uiManager); const hovering = hoverWrapper.value; const selected = useSelectedPlanet(uiManager).value; /* really bad way to do this but it works for now */ const [sending, setSending] = useState(false); useEffect(() => { const uiEmitter = UIEmitter.getInstance(); const setSendTrue = () => setSending(true); const setSendFalse = () => setSending(false); uiEmitter.addListener(UIEmitterEvent.SendCancelled, setSendFalse); uiEmitter.addListener(UIEmitterEvent.SendInitiated, setSendTrue); uiEmitter.addListener(UIEmitterEvent.SendCompleted, setSendFalse); return () => { uiEmitter.removeListener(UIEmitterEvent.SendCancelled, setSendFalse); uiEmitter.removeListener(UIEmitterEvent.SendInitiated, setSendTrue); uiEmitter.removeListener(UIEmitterEvent.SendCompleted, setSendFalse); }; }, []); const visible = useMemo( () => !!hovering && (hovering?.locationId !== selected?.locationId || !uiManager.getPlanetHoveringInRenderer()) && !sending && !uiManager.getMouseDownCoords(), [hovering, selected, sending, uiManager] ); return ( } /> ); } ================================================ FILE: src/Frontend/Panes/Lobbies/AdminPermissionsPane.tsx ================================================ import React from 'react'; import { Checkbox, DarkForestCheckbox } from '../../Components/Input'; import { Row } from '../../Components/Row'; import { LobbiesPaneProps, Warning } from './LobbiesUtils'; export function AdminPermissionsPane({ config, onUpdate }: LobbiesPaneProps) { return ( <> ) => onUpdate({ type: 'ADMIN_CAN_ADD_PLANETS', value: e.target.checked }) } /> {config.ADMIN_CAN_ADD_PLANETS.warning} ) => onUpdate({ type: 'WHITELIST_ENABLED', value: e.target.checked }) } /> {config.WHITELIST_ENABLED.warning} ); } ================================================ FILE: src/Frontend/Panes/Lobbies/ArtifactSettingsPane.tsx ================================================ import { ArtifactRarity } from '@darkforest_eth/types'; import React from 'react'; import { DarkForestNumberInput, NumberInput } from '../../Components/Input'; import { ArtifactRarityLabel } from '../../Components/Labels/ArtifactLabels'; import { Row } from '../../Components/Row'; import { LobbiesPaneProps, Warning } from './LobbiesUtils'; function ArtifactPointsPerRarity({ value, index, onUpdate, }: LobbiesPaneProps & { value: number | undefined; index: number }) { // We can skip Unknown if (index === 0) { return null; } return (
{/* TODO: We should have a utility that converts an integer into an ArtifactRarity safely */} ) => { onUpdate({ type: 'ARTIFACT_POINT_VALUES', value: e.target.value, index }); }} />
); } const pointsRowStyle = { gap: '8px' } as CSSStyleDeclaration & React.CSSProperties; export function ArtifactSettingsPane({ config, onUpdate }: LobbiesPaneProps) { return ( <> Photoid Cannon activation delay (in seconds) ) => { onUpdate({ type: 'PHOTOID_ACTIVATION_DELAY', value: e.target.value }); }} /> {config.PHOTOID_ACTIVATION_DELAY.warning} Artifact point values by rarity {(config.ARTIFACT_POINT_VALUES.displayValue ?? []).map((displayValue, idx) => ( ))} {config.ARTIFACT_POINT_VALUES.warning} ); } ================================================ FILE: src/Frontend/Panes/Lobbies/CaptureZonesPane.tsx ================================================ import _ from 'lodash'; import React from 'react'; import { Checkbox, DarkForestCheckbox, DarkForestNumberInput, NumberInput, } from '../../Components/Input'; import { Row } from '../../Components/Row'; import { DarkForestSlider, Slider } from '../../Components/Slider'; import { LobbiesPaneProps, Warning } from './LobbiesUtils'; function CaptureZoneScorePerLevel({ value, index, onUpdate, }: LobbiesPaneProps & { value: number | undefined; index: number }) { return (
Level {index} ) => { onUpdate({ type: 'CAPTURE_ZONE_PLANET_LEVEL_SCORE', value: e.target.value, index }); }} />
); } const rowChunkSize = 5; const rowStyle = { gap: '8px' } as CSSStyleDeclaration & React.CSSProperties; export function CaptureZonesPane({ config, onUpdate }: LobbiesPaneProps) { let captureZoneOptions = null; if (config.CAPTURE_ZONES_ENABLED.currentValue === true) { const scores = _.chunk(config.CAPTURE_ZONE_PLANET_LEVEL_SCORE.displayValue, rowChunkSize).map( (items, rowIdx) => { return ( {items.map((displayValue, idx) => ( ))} ); } ); captureZoneOptions = ( <> Radius of each Capture Zone ) => { onUpdate({ type: 'CAPTURE_ZONE_RADIUS', value: e.target.value }); }} /> ) => { onUpdate({ type: 'CAPTURE_ZONES_PER_5000_WORLD_RADIUS', value: e.target.value }); }} /> {config.CAPTURE_ZONES_PER_5000_WORLD_RADIUS.warning} ) => { onUpdate({ type: 'CAPTURE_ZONE_CHANGE_BLOCK_INTERVAL', value: e.target.value }); }} /> {config.CAPTURE_ZONE_CHANGE_BLOCK_INTERVAL.warning} Number of blocks between Invade & Capture ) => { onUpdate({ type: 'CAPTURE_ZONE_HOLD_BLOCKS_REQUIRED', value: e.target.value }); }} /> {config.CAPTURE_ZONE_HOLD_BLOCKS_REQUIRED.warning} {scores} {config.CAPTURE_ZONE_PLANET_LEVEL_SCORE.warning} ); } return ( <> ) => { onUpdate({ type: 'CAPTURE_ZONES_ENABLED', value: e.target.checked }); }} /> {config.CAPTURE_ZONES_ENABLED.warning} {captureZoneOptions} ); } ================================================ FILE: src/Frontend/Panes/Lobbies/ConfigurationPane.tsx ================================================ import { EthAddress } from '@darkforest_eth/types'; import _ from 'lodash'; import React, { useEffect, useReducer, useState } from 'react'; import { Route, Switch, useRouteMatch } from 'react-router-dom'; import { Btn } from '../../Components/Btn'; import { Spacer, Title } from '../../Components/CoreUI'; import { MythicLabelText } from '../../Components/Labels/MythicLabel'; import { LoadingSpinner } from '../../Components/LoadingSpinner'; import { Modal } from '../../Components/Modal'; import { Row } from '../../Components/Row'; import { TextPreview } from '../../Components/TextPreview'; import { AdminPermissionsPane } from './AdminPermissionsPane'; import { ArtifactSettingsPane } from './ArtifactSettingsPane'; import { CaptureZonesPane } from './CaptureZonesPane'; import { GameSettingsPane } from './GameSettingsPane'; import { ButtonRow, ConfigDownload, ConfigUpload, LinkButton, LobbiesPaneProps, NavigationTitle, Warning, } from './LobbiesUtils'; import { MinimapConfig } from './MinimapUtils'; import { PlanetPane } from './PlanetPane'; import { PlayerSpawnPane } from './PlayerSpawnPane'; import { InvalidConfigError, LobbyConfigAction, lobbyConfigInit, lobbyConfigReducer, LobbyInitializers, toInitializers, } from './Reducer'; import { SnarkPane } from './SnarkPane'; import { SpaceJunkPane } from './SpaceJunkPane'; import { SpaceTypeBiomePane } from './SpaceTypeBiomePane'; import { WorldSizePane } from './WorldSizePane'; interface PaneConfig { title: string; shortcut: string; path: string; Pane: (props: LobbiesPaneProps) => JSX.Element; } const panes: ReadonlyArray = [ { title: 'Game settings', shortcut: `1`, path: '/settings/game', Pane: (props: LobbiesPaneProps) => , }, { title: 'World size', shortcut: `2`, path: '/settings/world', Pane: (props: LobbiesPaneProps) => , }, { title: 'Space type & Biome', shortcut: `3`, path: '/settings/space', Pane: (props: LobbiesPaneProps) => , }, { title: 'Planets', shortcut: `4`, path: '/settings/planet', Pane: (props: LobbiesPaneProps) => , }, { title: 'Player spawn', shortcut: `5`, path: '/settings/spawn', Pane: (props: LobbiesPaneProps) => , }, { title: 'Space junk', shortcut: `6`, path: '/settings/junk', Pane: (props: LobbiesPaneProps) => , }, { title: 'Capture zones', shortcut: `7`, path: '/settings/zones', Pane: (props: LobbiesPaneProps) => , }, { title: 'Artifacts', shortcut: `8`, path: '/settings/artifact', Pane: (props: LobbiesPaneProps) => , }, { title: 'Admin permissions', shortcut: `9`, path: '/settings/admin', Pane: (props: LobbiesPaneProps) => , }, { title: 'Advanced: Snarks', shortcut: `0`, path: '/settings/snark', Pane: (props: LobbiesPaneProps) => , }, ] as const; type Status = 'creating' | 'created' | 'errored' | undefined; function ConfigurationNavigation({ error, lobbyAddress, status, onCreate, }: { error: string | undefined; lobbyAddress: EthAddress | undefined; status: Status; onCreate: () => Promise; }) { const buttons = _.chunk(panes, 2).map(([fst, snd], idx) => { return ( // Index key is fine here because the array is stable {fst && ( {fst.title} )} {snd && ( {snd.title} )} ); }); const url = process.env.NODE_ENV === 'production' ? `${window.DEPLOY_URL}/play/${lobbyAddress}` : `${window.location.origin}/play/${lobbyAddress}`; let lobbyContent; if (status === 'created' && lobbyAddress) { lobbyContent = ( <> window.open(url)}> Launch Lobby {/* Stealing MythicLabelText because it accepts variable text input */} You can also share the direct url with your friends: {/* Didn't like the TextPreview jumping, so I'm setting the height */} ); } const createDisabled = status === 'creating' || status === 'created'; const creating = status === 'creating' || (status === 'created' && !lobbyAddress); return ( <> Customize Lobby
Welcome Cadet! You can launch a copy of Dark Forest from this UI. We call this a Lobby. All settings will be defaulted to the same configuration of the main contract you are copying. However, you can change any of those settings through the buttons below!
{buttons} {creating ? : 'Create Lobby'} {error} {lobbyContent} ); } export function ConfigurationPane({ modalIndex, lobbyAddress, startingConfig, onMapChange, onCreate, }: { modalIndex: number; lobbyAddress: EthAddress | undefined; startingConfig: LobbyInitializers; onMapChange: (props: MinimapConfig) => void; onCreate: (config: LobbyInitializers) => Promise; }) { const { path: root } = useRouteMatch(); const [error, setError] = useState(); const [status, setStatus] = useState(undefined); // Separated IO Errors from Download/Upload so they show on any pane of the modal const [ioErr, setIoErr] = useState(); const [config, updateConfig] = useReducer(lobbyConfigReducer, startingConfig, lobbyConfigInit); function onUpdate(action: LobbyConfigAction) { setError(undefined); setIoErr(undefined); updateConfig(action); } // Minimap only changes on a subset of properties, so we only trigger when one of them changes value (and still debounce it) useEffect(() => { onMapChange({ worldRadius: config.WORLD_RADIUS_MIN.currentValue, key: config.SPACETYPE_KEY.currentValue, scale: config.PERLIN_LENGTH_SCALE.currentValue, mirrorX: config.PERLIN_MIRROR_X.currentValue, mirrorY: config.PERLIN_MIRROR_Y.currentValue, perlinThreshold1: config.PERLIN_THRESHOLD_1.currentValue, perlinThreshold2: config.PERLIN_THRESHOLD_2.currentValue, perlinThreshold3: config.PERLIN_THRESHOLD_3.currentValue, }); }, [ onMapChange, config.WORLD_RADIUS_MIN.currentValue, config.SPACETYPE_KEY.currentValue, config.PERLIN_LENGTH_SCALE.currentValue, config.PERLIN_MIRROR_X.currentValue, config.PERLIN_MIRROR_Y.currentValue, config.PERLIN_THRESHOLD_1.currentValue, config.PERLIN_THRESHOLD_2.currentValue, config.PERLIN_THRESHOLD_3.currentValue, ]); const routes = panes.map(({ title, path, Pane }, idx) => { return ( // Index key is fine here because the array is stable {title} ); }); async function validateAndCreateLobby() { try { setStatus('creating'); const initializers = toInitializers(config); await onCreate(initializers); setStatus('created'); } catch (err) { setStatus('errored'); console.error(err); if (err instanceof InvalidConfigError) { setError(`Invalid ${err.key} value ${err.value ?? ''} - ${err.message}`); } else { setError(err?.message || 'Something went wrong. Check your dev console.'); } } } function configUploadSuccess(initializers: LobbyInitializers) { updateConfig({ type: 'RESET', value: lobbyConfigInit(initializers) }); } return ( {routes} {/* Button this in the title slot but at the end moves it to the end of the title bar */} {ioErr} ); } ================================================ FILE: src/Frontend/Panes/Lobbies/GameSettingsPane.tsx ================================================ import React from 'react'; import { Checkbox, DarkForestCheckbox, DarkForestNumberInput, NumberInput, } from '../../Components/Input'; import { Row } from '../../Components/Row'; import { DarkForestSlider, Slider } from '../../Components/Slider'; import { LobbiesPaneProps, Warning } from './LobbiesUtils'; export function GameSettingsPane({ config, onUpdate }: LobbiesPaneProps) { return ( <> ) => { onUpdate({ type: 'TIME_FACTOR_HUNDREDTHS', value: e.target.value }); }} /> {config.TIME_FACTOR_HUNDREDTHS.warning} ) => { onUpdate({ type: 'PLANET_TRANSFER_ENABLED', value: e.target.checked }); }} /> {config.PLANET_TRANSFER_ENABLED.warning} Location reveal cooldown (in seconds) ) => { onUpdate({ type: 'LOCATION_REVEAL_COOLDOWN', value: e.target.value }); }} /> {config.LOCATION_REVEAL_COOLDOWN.warning} {/* It is a little weird that this is in Game Settings, but I'd rather keep other scoring grouped */} Amount of points for each silver withdrawn ) => { onUpdate({ type: 'SILVER_SCORE_VALUE', value: e.target.value }); }} /> {config.SILVER_SCORE_VALUE.warning} ); } ================================================ FILE: src/Frontend/Panes/Lobbies/LobbiesUtils.tsx ================================================ /** This file contains some common utilities used by the Lobbies UI */ import { Initializers } from '@darkforest_eth/settings'; import { EthAddress } from '@darkforest_eth/types'; import React from 'react'; import { useHistory, useRouteMatch } from 'react-router-dom'; import styled from 'styled-components'; import { Btn, ShortcutBtn } from '../../Components/Btn'; import { Title } from '../../Components/CoreUI'; import { Row } from '../../Components/Row'; import { Red } from '../../Components/Text'; import { LobbyConfigAction, LobbyConfigState, toInitializers } from './Reducer'; export interface LobbiesPaneProps { config: LobbyConfigState; onUpdate: (change: LobbyConfigAction) => void; } export const ButtonRow = styled(Row)` gap: 8px; .button { flex: 1 1 50%; } `; export function LinkButton({ to, shortcut, children, }: React.PropsWithChildren<{ to: string; shortcut?: string }>) { const { url } = useRouteMatch(); const history = useHistory(); function navigate() { history.push(`${url}${to}`); } // Adding className="button" so ButtonRow will add the flex stuff return ( {children} ); } export function NavigationTitle({ children }: React.PropsWithChildren) { const history = useHistory(); const shortcut = 't'; function goBack() { history.goBack(); } return ( <> back {children} ); } export function Warning({ children }: React.PropsWithChildren) { if (!children) { return null; } else { return (
Error: {children}
); } } export function ConfigDownload({ onError, address, config, }: { onError: (msg: string) => void; address: EthAddress | undefined; config: LobbyConfigState; }) { function doDownload() { try { const initializers = toInitializers(config); const blob = new Blob([JSON.stringify(initializers, null, 2)], { type: 'application/json' }); const name = address ? `${address.substring(0, 6)}-lobbies-config.json` : 'lobbies-config.json'; const blobAsUrl = (window.webkitURL || window.URL).createObjectURL(blob); const anchor = document.createElement('a'); anchor.href = blobAsUrl; anchor.download = name; anchor.click(); } catch (err) { console.error(err); onError('Unable to download config file'); } } return ( Download ); } export function ConfigUpload({ onError, onUpload, }: { onError: (msg: string) => void; onUpload: (initializers: Initializers) => void; }) { function doUpload() { const reader = new FileReader(); reader.onload = () => { if (typeof reader.result === 'string') { try { onUpload(JSON.parse(reader.result)); } catch (err) { onError('Cannot process uploaded JSON'); } } else { onError('Could not read uploaded file'); } }; const inputFile = document.createElement('input'); inputFile.type = 'file'; inputFile.onchange = () => { try { const file = inputFile.files?.item(0); if (file) { reader.readAsText(file); } else { onError('Could not find a file to upload'); } } catch (err) { console.error(err); onError('Upload failed'); } }; inputFile.click(); } return ( Upload ); } ================================================ FILE: src/Frontend/Panes/Lobbies/MinimapPane.tsx ================================================ import React, { useEffect, useMemo, useRef, useState } from 'react'; import { LoadingSpinner } from '../../Components/LoadingSpinner'; import { Modal } from '../../Components/Modal'; import { DrawMessage, MinimapConfig } from './MinimapUtils'; function getWorker() { return new Worker(new URL('./minimap.worker.ts', import.meta.url)); } function drawOnCanvas(canvas: HTMLCanvasElement | null, msg: DrawMessage) { if (!canvas) { console.error(`No canvas to draw to`); return; } const ctx = canvas.getContext('2d'); if (!ctx) { console.error(`Couldn't get the planet context`); return; } ctx.clearRect(0, 0, canvas.width, canvas.height); const dot = 4; const sizeFactor = 380; const { radius, data } = msg; const normalize = (val: number) => { return Math.floor(((val + radius) * sizeFactor) / (radius * 2)); }; // draw mini-map for (let i = 0; i < data.length; i++) { if (data[i].type === 0) { ctx.fillStyle = '#186469'; // inner nebula } else if (data[i].type === 1) { ctx.fillStyle = '#24247d'; // outer nebula } else if (data[i].type === 2) { ctx.fillStyle = '#000000'; // deep space } else if (data[i].type === 3) { ctx.fillStyle = '#038700'; // dead space } ctx.fillRect(normalize(data[i].x) + 10, normalize(data[i].y * -1) + 10, dot, dot); } // draw extents of map const radiusNormalized = normalize(radius) / 2; ctx.beginPath(); ctx.arc(radiusNormalized + 12, radiusNormalized + 12, radiusNormalized, 0, 2 * Math.PI); ctx.strokeStyle = '#DDDDDD'; ctx.lineWidth = 2; ctx.stroke(); } export function Minimap({ modalIndex, config, }: { modalIndex: number; config: MinimapConfig | undefined; }) { const canvasRef = useRef(null); const [refreshing, setRefreshing] = useState(false); const worker = useMemo(getWorker, []); useEffect(() => { if (config) { setRefreshing(true); worker.postMessage(JSON.stringify(config)); } }, [worker, config, setRefreshing]); useEffect(() => { function onMessage(e: MessageEvent) { if (e.data) { drawOnCanvas(canvasRef.current, JSON.parse(e.data)); setRefreshing(false); } } worker.addEventListener('message', onMessage); return () => worker.removeEventListener('message', onMessage); }, [worker, setRefreshing]); return (
World Minimap
{refreshing ? : null}
); } ================================================ FILE: src/Frontend/Panes/Lobbies/MinimapUtils.ts ================================================ import { SpaceType } from '@darkforest_eth/types'; export type MinimapConfig = { worldRadius: number; // perlin key: number; scale: number; mirrorX: boolean; mirrorY: boolean; perlinThreshold1: number; perlinThreshold2: number; perlinThreshold3: number; }; export type DrawMessage = { radius: number; data: { x: number; y: number; type: SpaceType }[]; }; ================================================ FILE: src/Frontend/Panes/Lobbies/PlanetPane.tsx ================================================ import _ from 'lodash'; import React from 'react'; import { DarkForestNumberInput, NumberInput } from '../../Components/Input'; import { Row } from '../../Components/Row'; import { DarkForestSlider, Slider } from '../../Components/Slider'; import { Sub } from '../../Components/Text'; import { LobbiesPaneProps, Warning } from './LobbiesUtils'; const rowChunkSize = 5; const rowStyle = { gap: '8px' } as CSSStyleDeclaration & React.CSSProperties; // Handling the non-input lvl 0 by calculating the items in the row const itemStyle = { flex: `1 1 ${Math.floor(100 / rowChunkSize)}%` }; function ThresholdByPlanetLevel({ index, value, onUpdate, }: LobbiesPaneProps & { value: number | undefined; index: number }) { // The level 0 value can never change if (index === 0) { return (
Level 0
{value}
); } else { return (
Level {index} ) => { onUpdate({ type: 'PLANET_LEVEL_THRESHOLDS', index, value: e.target.value }); }} />
); } } export function PlanetPane({ config, onUpdate }: LobbiesPaneProps) { let planetLevelThresholds = null; if (config.PLANET_LEVEL_THRESHOLDS.displayValue) { planetLevelThresholds = _.chunk(config.PLANET_LEVEL_THRESHOLDS.displayValue, rowChunkSize).map( (items, rowIdx) => { return ( {items.map((displayValue, idx) => ( ))} ); } ); } return ( <> Planet rarity: ) => { onUpdate({ type: 'PLANET_RARITY', value: e.target.value }); }} /> {config.PLANET_RARITY.warning} ) => onUpdate({ type: 'MAX_NATURAL_PLANET_LEVEL', value: e.target.value }) } /> {config.MAX_NATURAL_PLANET_LEVEL.warning} Advanced: Frequency of planet levels {planetLevelThresholds} {config.PLANET_LEVEL_THRESHOLDS.warning} ); } ================================================ FILE: src/Frontend/Panes/Lobbies/PlayerSpawnPane.tsx ================================================ import React from 'react'; import { DarkForestNumberInput, NumberInput } from '../../Components/Input'; import { Row } from '../../Components/Row'; import { DarkForestSliderHandle, Slider, SliderHandle } from '../../Components/Slider'; import { LobbiesPaneProps, Warning } from './LobbiesUtils'; export function PlayerSpawnPane({ config, onUpdate }: LobbiesPaneProps) { return ( <> ) => { onUpdate({ type: 'INIT_PERLIN_MIN', value: e.target.value }); }} /> ) => { onUpdate({ type: 'INIT_PERLIN_MAX', value: e.target.value }); }} /> {config.INIT_PERLIN_MIN.warning || config.INIT_PERLIN_MAX.warning} {/* TODO: Explain this better in Help content */} Equivalent spawnable radius ) => { onUpdate({ type: 'SPAWN_RIM_AREA', value: e.target.value }); }} /> {config.SPAWN_RIM_AREA.warning} ); } ================================================ FILE: src/Frontend/Panes/Lobbies/Reducer.ts ================================================ import { Initializers } from '@darkforest_eth/settings'; export const SAFE_UPPER_BOUNDS = Number.MAX_SAFE_INTEGER - 1; export class InvalidConfigError extends Error { key: string; value: unknown | undefined; constructor(msg: string, key: string, value: unknown) { super(msg); this.key = key; // Handling number and strings - if we add an object value, this will assign it if (!Array.isArray(value)) { this.value = value; } } } // Throws an error if any warnings exist export function toInitializers(obj: LobbyConfigState) { const filtered: Record = {}; for (const [key, { currentValue, displayValue, warning }] of Object.entries(obj)) { if (warning) { // displayValue is the invalid value, so we use that as the "value" throw new InvalidConfigError(warning, key, displayValue); } filtered[key] = currentValue; } return filtered as LobbyInitializers; } // Actions aren't 1-to-1 with Initializers because we sometimes need to update into arrays export type LobbyConfigAction = | { type: 'START_PAUSED'; value: Initializers['START_PAUSED'] | undefined } | { type: 'ADMIN_CAN_ADD_PLANETS'; value: Initializers['ADMIN_CAN_ADD_PLANETS'] | undefined } | { type: 'TOKEN_MINT_END_TIMESTAMP'; value: Initializers['TOKEN_MINT_END_TIMESTAMP'] | undefined; } | { type: 'WORLD_RADIUS_LOCKED'; value: Initializers['WORLD_RADIUS_LOCKED'] | undefined } | { type: 'WORLD_RADIUS_MIN'; value: Initializers['WORLD_RADIUS_MIN'] | undefined } | { type: 'DISABLE_ZK_CHECKS'; value: Initializers['DISABLE_ZK_CHECKS'] | undefined } | { type: 'PLANETHASH_KEY'; value: Initializers['PLANETHASH_KEY'] | undefined } | { type: 'SPACETYPE_KEY'; value: Initializers['SPACETYPE_KEY'] | undefined } | { type: 'BIOMEBASE_KEY'; value: Initializers['BIOMEBASE_KEY'] | undefined } | { type: 'PERLIN_MIRROR_X'; value: Initializers['PERLIN_MIRROR_X'] | undefined } | { type: 'PERLIN_MIRROR_Y'; value: Initializers['PERLIN_MIRROR_Y'] | undefined } | { type: 'PERLIN_LENGTH_SCALE'; value: Initializers['PERLIN_LENGTH_SCALE'] | undefined } | { type: 'MAX_NATURAL_PLANET_LEVEL'; value: Initializers['MAX_NATURAL_PLANET_LEVEL'] | undefined; } | { type: 'TIME_FACTOR_HUNDREDTHS'; value: Initializers['TIME_FACTOR_HUNDREDTHS'] | undefined } | { type: 'PERLIN_THRESHOLD_1'; value: Initializers['PERLIN_THRESHOLD_1'] | undefined } | { type: 'PERLIN_THRESHOLD_2'; value: Initializers['PERLIN_THRESHOLD_2'] | undefined } | { type: 'PERLIN_THRESHOLD_3'; value: Initializers['PERLIN_THRESHOLD_3'] | undefined } | { type: 'INIT_PERLIN_MIN'; value: Initializers['INIT_PERLIN_MIN'] | undefined } | { type: 'INIT_PERLIN_MAX'; value: Initializers['INIT_PERLIN_MAX'] | undefined } | { type: 'BIOME_THRESHOLD_1'; value: Initializers['BIOME_THRESHOLD_1'] | undefined } | { type: 'BIOME_THRESHOLD_2'; value: Initializers['BIOME_THRESHOLD_2'] | undefined } | { type: 'PLANET_LEVEL_THRESHOLDS'; value: number | undefined; index: number } | { type: 'PLANET_RARITY'; value: Initializers['PLANET_RARITY'] | undefined } | { type: 'PLANET_TRANSFER_ENABLED'; value: Initializers['PLANET_TRANSFER_ENABLED'] | undefined } | { type: 'PHOTOID_ACTIVATION_DELAY'; value: Initializers['PHOTOID_ACTIVATION_DELAY'] | undefined; } | { type: 'SPAWN_RIM_AREA'; value: Initializers['SPAWN_RIM_AREA'] | undefined } | { type: 'LOCATION_REVEAL_COOLDOWN'; value: Initializers['LOCATION_REVEAL_COOLDOWN'] | undefined; } // TODO(#2134): Add to UI when this scoring functionality is re-enabled // | { type: 'CLAIM_PLANET_COOLDOWN'; value: Initializers['CLAIM_PLANET_COOLDOWN'] | undefined } | { type: 'PLANET_TYPE_WEIGHTS'; value: Initializers['PLANET_TYPE_WEIGHTS'] | undefined } | { type: 'SILVER_SCORE_VALUE'; value: Initializers['SILVER_SCORE_VALUE'] | undefined } | { type: 'ARTIFACT_POINT_VALUES'; value: number | undefined; index: number; } | { type: 'SPACE_JUNK_ENABLED'; value: Initializers['SPACE_JUNK_ENABLED'] | undefined } | { type: 'SPACE_JUNK_LIMIT'; value: Initializers['SPACE_JUNK_LIMIT'] | undefined } | { type: 'PLANET_LEVEL_JUNK'; index: number; value: number | undefined } | { type: 'ABANDON_SPEED_CHANGE_PERCENT'; value: Initializers['ABANDON_SPEED_CHANGE_PERCENT'] | undefined; } | { type: 'ABANDON_RANGE_CHANGE_PERCENT'; value: Initializers['ABANDON_RANGE_CHANGE_PERCENT'] | undefined; } | { type: 'CAPTURE_ZONES_ENABLED'; value: Initializers['CAPTURE_ZONES_ENABLED'] | undefined } // TODO(#2299): Add to UI when this functionality is implemented // | { type: 'CAPTURE_ZONE_COUNT'; value: Initializers['CAPTURE_ZONE_COUNT'] | undefined } | { type: 'CAPTURE_ZONE_CHANGE_BLOCK_INTERVAL'; value: Initializers['CAPTURE_ZONE_CHANGE_BLOCK_INTERVAL'] | undefined; } | { type: 'CAPTURE_ZONE_RADIUS'; value: Initializers['CAPTURE_ZONE_RADIUS'] | undefined } | { type: 'CAPTURE_ZONE_PLANET_LEVEL_SCORE'; value: number | undefined; index: number; } | { type: 'CAPTURE_ZONE_HOLD_BLOCKS_REQUIRED'; value: Initializers['CAPTURE_ZONE_HOLD_BLOCKS_REQUIRED'] | undefined; } | { type: 'CAPTURE_ZONES_PER_5000_WORLD_RADIUS'; value: Initializers['CAPTURE_ZONES_PER_5000_WORLD_RADIUS'] | undefined; } | { type: 'WHITELIST_ENABLED'; value: boolean | undefined }; // TODO(#2328): WHITELIST_ENABLED should just be on Initializers export type LobbyInitializers = Initializers & { WHITELIST_ENABLED: boolean | undefined }; export type LobbyConfigState = { [key in keyof LobbyInitializers]: { currentValue: LobbyInitializers[key]; displayValue: Partial | undefined; defaultValue: LobbyInitializers[key]; warning: string | undefined; }; }; export type LobbyAction = { type: 'RESET'; value: LobbyConfigState } | LobbyConfigAction; export function lobbyConfigReducer(state: LobbyConfigState, action: LobbyAction) { let update; switch (action.type) { case 'START_PAUSED': { update = ofBoolean(action, state); break; } case 'ADMIN_CAN_ADD_PLANETS': { update = ofBoolean(action, state); break; } case 'TOKEN_MINT_END_TIMESTAMP': { // TODO: Date update = ofAny(action, state); break; } case 'WORLD_RADIUS_LOCKED': { update = ofBoolean(action, state); break; } case 'WORLD_RADIUS_MIN': { update = ofWorldRadiusMin(action, state); break; } case 'DISABLE_ZK_CHECKS': { update = ofBoolean(action, state); break; } case 'PLANETHASH_KEY': { update = ofPositiveInteger(action, state); break; } case 'SPACETYPE_KEY': { update = ofPositiveInteger(action, state); break; } case 'BIOMEBASE_KEY': { update = ofPositiveInteger(action, state); break; } case 'PERLIN_MIRROR_X': { update = ofBoolean(action, state); break; } case 'PERLIN_MIRROR_Y': { update = ofBoolean(action, state); break; } case 'PERLIN_LENGTH_SCALE': { update = ofPerlinLengthScale(action, state); break; } case 'MAX_NATURAL_PLANET_LEVEL': { update = ofMaxNaturalPlanetLevel(action, state); break; } case 'TIME_FACTOR_HUNDREDTHS': { update = ofTimeFactorHundredths(action, state); break; } case 'PERLIN_THRESHOLD_1': { update = ofPositiveInteger(action, state); break; } case 'PERLIN_THRESHOLD_2': { update = ofPositiveInteger(action, state); break; } case 'PERLIN_THRESHOLD_3': { update = ofPositiveInteger(action, state); break; } case 'INIT_PERLIN_MIN': { update = ofPositiveInteger(action, state); break; } case 'INIT_PERLIN_MAX': { update = ofPositiveInteger(action, state); break; } case 'BIOME_THRESHOLD_1': { update = ofPositiveInteger(action, state); break; } case 'BIOME_THRESHOLD_2': { update = ofPositiveInteger(action, state); break; } case 'PLANET_LEVEL_THRESHOLDS': { update = ofPlanetLevelThresholds(action, state); break; } case 'PLANET_RARITY': { update = ofPlanetRarity(action, state); break; } case 'PLANET_TRANSFER_ENABLED': { update = ofBoolean(action, state); break; } case 'PHOTOID_ACTIVATION_DELAY': { update = ofPositiveInteger(action, state); break; } case 'SPAWN_RIM_AREA': { update = ofSpawnRimArea(action, state); break; } case 'LOCATION_REVEAL_COOLDOWN': { update = ofPositiveInteger(action, state); break; } // TODO(#2134): Add to UI when this scoring functionality is re-enabled // case 'CLAIM_PLANET_COOLDOWN': { // update = ofPositiveInteger(action, state); // break; // } case 'PLANET_TYPE_WEIGHTS': { // TODO: Add this update = ofNoop(action, state); break; } case 'SILVER_SCORE_VALUE': { update = ofPositivePercent(action, state); break; } case 'ARTIFACT_POINT_VALUES': { update = ofArtifactPointValues(action, state); break; } case 'SPACE_JUNK_ENABLED': { update = ofBoolean(action, state); break; } case 'SPACE_JUNK_LIMIT': { update = ofSpaceJunkLimit(action, state); break; } case 'PLANET_LEVEL_JUNK': { update = ofPlanetLevelJunk(action, state); break; } case 'ABANDON_SPEED_CHANGE_PERCENT': { update = ofPositivePercent(action, state); break; } case 'ABANDON_RANGE_CHANGE_PERCENT': { update = ofPositivePercent(action, state); break; } case 'CAPTURE_ZONES_ENABLED': { update = ofBoolean(action, state); break; } // TODO(#2299): Add to UI when this functionality is implemented // case 'CAPTURE_ZONE_COUNT': { // update = ofPositiveInteger(action, state); // break; // } case 'CAPTURE_ZONE_CHANGE_BLOCK_INTERVAL': { update = ofCaptureZoneChangeBlockInterval(action, state); break; } case 'CAPTURE_ZONE_RADIUS': { update = ofCaptureZoneRadius(action, state); break; } case 'CAPTURE_ZONE_PLANET_LEVEL_SCORE': { update = ofCaptureZonePlanetLevelScore(action, state); break; } case 'CAPTURE_ZONE_HOLD_BLOCKS_REQUIRED': { update = ofPositiveInteger(action, state); break; } case 'CAPTURE_ZONES_PER_5000_WORLD_RADIUS': { update = ofCaptureZonesPer5000WorldRadius(action, state); break; } case 'WHITELIST_ENABLED': { update = ofBoolean(action, state); break; } case 'RESET': { // Hard reset all values that were available in the JSON return { ...state, ...action.value, }; } default: { // https://www.typescriptlang.org/docs/handbook/2/narrowing.html#exhaustiveness-checking const _exhaustive: never = action; // Return the state if somehow we hit this return state; } } return { ...state, [action.type]: update, }; } export function lobbyConfigInit(startingConfig: LobbyInitializers) { const state: Partial = {}; for (const key of Object.keys(startingConfig) as [keyof LobbyInitializers]) { switch (key) { case 'START_PAUSED': { const defaultValue = startingConfig[key]; state[key] = { currentValue: defaultValue, displayValue: defaultValue, defaultValue, warning: undefined, }; break; } case 'ADMIN_CAN_ADD_PLANETS': { const defaultValue = startingConfig[key]; state[key] = { currentValue: defaultValue, displayValue: defaultValue, defaultValue, warning: undefined, }; break; } case 'TOKEN_MINT_END_TIMESTAMP': { // TODO: Handle dates const defaultValue = startingConfig[key]; state[key] = { currentValue: defaultValue, displayValue: defaultValue, defaultValue, warning: undefined, }; break; } case 'WORLD_RADIUS_LOCKED': { const defaultValue = startingConfig[key]; state[key] = { currentValue: defaultValue, displayValue: defaultValue, defaultValue, warning: undefined, }; break; } case 'WORLD_RADIUS_MIN': { const defaultValue = startingConfig[key]; state[key] = { currentValue: defaultValue, displayValue: defaultValue, defaultValue, warning: undefined, }; break; } case 'DISABLE_ZK_CHECKS': { const defaultValue = startingConfig[key]; state[key] = { currentValue: defaultValue, displayValue: defaultValue, defaultValue, warning: undefined, }; break; } case 'PLANETHASH_KEY': { const defaultValue = startingConfig[key]; state[key] = { currentValue: defaultValue, displayValue: defaultValue, defaultValue, warning: undefined, }; break; } case 'SPACETYPE_KEY': { const defaultValue = startingConfig[key]; state[key] = { currentValue: defaultValue, displayValue: defaultValue, defaultValue, warning: undefined, }; break; } case 'BIOMEBASE_KEY': { const defaultValue = startingConfig[key]; state[key] = { currentValue: defaultValue, displayValue: defaultValue, defaultValue, warning: undefined, }; break; } case 'PERLIN_MIRROR_X': { const defaultValue = startingConfig[key]; state[key] = { currentValue: defaultValue, displayValue: defaultValue, defaultValue, warning: undefined, }; break; } case 'PERLIN_MIRROR_Y': { const defaultValue = startingConfig[key]; state[key] = { currentValue: defaultValue, displayValue: defaultValue, defaultValue, warning: undefined, }; break; } case 'PERLIN_LENGTH_SCALE': { const defaultValue = startingConfig[key]; state[key] = { currentValue: defaultValue, displayValue: Math.log2(defaultValue), defaultValue, warning: undefined, }; break; } case 'MAX_NATURAL_PLANET_LEVEL': { const defaultValue = startingConfig[key]; state[key] = { currentValue: defaultValue, displayValue: defaultValue, defaultValue, warning: undefined, }; break; } case 'TIME_FACTOR_HUNDREDTHS': { const defaultValue = startingConfig[key]; state[key] = { currentValue: defaultValue, displayValue: Math.floor(defaultValue / 100), defaultValue, warning: undefined, }; break; } case 'PERLIN_THRESHOLD_1': { const defaultValue = startingConfig[key]; state[key] = { currentValue: defaultValue, displayValue: defaultValue, defaultValue, warning: undefined, }; break; } case 'PERLIN_THRESHOLD_2': { const defaultValue = startingConfig[key]; state[key] = { currentValue: defaultValue, displayValue: defaultValue, defaultValue, warning: undefined, }; break; } case 'PERLIN_THRESHOLD_3': { const defaultValue = startingConfig[key]; state[key] = { currentValue: defaultValue, displayValue: defaultValue, defaultValue, warning: undefined, }; break; } case 'INIT_PERLIN_MIN': { const defaultValue = startingConfig[key]; state[key] = { currentValue: defaultValue, displayValue: defaultValue, defaultValue, warning: undefined, }; break; } case 'INIT_PERLIN_MAX': { const defaultValue = startingConfig[key]; state[key] = { currentValue: defaultValue, displayValue: defaultValue, defaultValue, warning: undefined, }; break; } case 'BIOME_THRESHOLD_1': { const defaultValue = startingConfig[key]; state[key] = { currentValue: defaultValue, displayValue: defaultValue, defaultValue, warning: undefined, }; break; } case 'BIOME_THRESHOLD_2': { const defaultValue = startingConfig[key]; state[key] = { currentValue: defaultValue, displayValue: defaultValue, defaultValue, warning: undefined, }; break; } case 'PLANET_LEVEL_THRESHOLDS': { const defaultValue = startingConfig[key]; state[key] = { currentValue: defaultValue, displayValue: defaultValue, defaultValue, warning: undefined, }; break; } case 'PLANET_RARITY': { const defaultValue = startingConfig[key]; state[key] = { currentValue: defaultValue, displayValue: defaultValue, defaultValue, warning: undefined, }; break; } case 'PLANET_TRANSFER_ENABLED': { const defaultValue = startingConfig[key]; state[key] = { currentValue: defaultValue, displayValue: defaultValue, defaultValue, warning: undefined, }; break; } case 'PHOTOID_ACTIVATION_DELAY': { const defaultValue = startingConfig[key]; state[key] = { currentValue: defaultValue, displayValue: defaultValue, defaultValue, warning: undefined, }; break; } case 'SPAWN_RIM_AREA': { const defaultValue = startingConfig[key]; state[key] = { currentValue: defaultValue, displayValue: Math.sqrt(defaultValue / Math.PI), defaultValue, warning: undefined, }; break; } case 'LOCATION_REVEAL_COOLDOWN': { const defaultValue = startingConfig[key]; state[key] = { currentValue: defaultValue, displayValue: defaultValue, defaultValue, warning: undefined, }; break; } case 'CLAIM_PLANET_COOLDOWN': { const defaultValue = startingConfig[key]; state[key] = { currentValue: defaultValue, displayValue: defaultValue, defaultValue, warning: undefined, }; break; } case 'PLANET_TYPE_WEIGHTS': { const defaultValue = startingConfig[key]; state[key] = { currentValue: defaultValue, displayValue: defaultValue, defaultValue, warning: undefined, }; break; } case 'SILVER_SCORE_VALUE': { const defaultValue = startingConfig[key]; state[key] = { currentValue: defaultValue, displayValue: defaultValue / 100, defaultValue, warning: undefined, }; break; } case 'ARTIFACT_POINT_VALUES': { const defaultValue = startingConfig[key]; state[key] = { currentValue: defaultValue, displayValue: defaultValue, defaultValue, warning: undefined, }; break; } case 'SPACE_JUNK_ENABLED': { const defaultValue = startingConfig[key]; state[key] = { currentValue: defaultValue, displayValue: defaultValue, defaultValue, warning: undefined, }; break; } case 'SPACE_JUNK_LIMIT': { const defaultValue = startingConfig[key]; state[key] = { currentValue: defaultValue, displayValue: defaultValue, defaultValue, warning: undefined, }; break; } case 'PLANET_LEVEL_JUNK': { const defaultValue = startingConfig[key]; state[key] = { currentValue: defaultValue, displayValue: defaultValue, defaultValue, warning: undefined, }; break; } case 'ABANDON_SPEED_CHANGE_PERCENT': { const defaultValue = startingConfig[key]; state[key] = { currentValue: defaultValue, displayValue: defaultValue / 100, defaultValue, warning: undefined, }; break; } case 'ABANDON_RANGE_CHANGE_PERCENT': { const defaultValue = startingConfig[key]; state[key] = { currentValue: defaultValue, displayValue: defaultValue / 100, defaultValue, warning: undefined, }; break; } case 'CAPTURE_ZONES_ENABLED': { const defaultValue = startingConfig[key]; state[key] = { currentValue: defaultValue, displayValue: defaultValue, defaultValue, warning: undefined, }; break; } case 'CAPTURE_ZONE_COUNT': { const defaultValue = startingConfig[key]; state[key] = { currentValue: defaultValue, displayValue: defaultValue, defaultValue, warning: undefined, }; break; } case 'CAPTURE_ZONE_CHANGE_BLOCK_INTERVAL': { const defaultValue = startingConfig[key]; state[key] = { currentValue: defaultValue, displayValue: defaultValue, defaultValue, warning: undefined, }; break; } case 'CAPTURE_ZONE_RADIUS': { const defaultValue = startingConfig[key]; state[key] = { currentValue: defaultValue, displayValue: defaultValue, defaultValue, warning: undefined, }; break; } case 'CAPTURE_ZONE_PLANET_LEVEL_SCORE': { const defaultValue = startingConfig[key]; state[key] = { currentValue: defaultValue, displayValue: defaultValue, defaultValue, warning: undefined, }; break; } case 'CAPTURE_ZONE_HOLD_BLOCKS_REQUIRED': { const defaultValue = startingConfig[key]; state[key] = { currentValue: defaultValue, displayValue: defaultValue, defaultValue, warning: undefined, }; break; } case 'CAPTURE_ZONES_PER_5000_WORLD_RADIUS': { const defaultValue = startingConfig[key]; state[key] = { currentValue: defaultValue, displayValue: defaultValue, defaultValue, warning: undefined, }; break; } case 'WHITELIST_ENABLED': { // Default this to false if we don't have it const defaultValue = startingConfig[key] || false; state[key] = { currentValue: defaultValue, displayValue: defaultValue, defaultValue, warning: undefined, }; break; } default: { // https://www.typescriptlang.org/docs/handbook/2/narrowing.html#exhaustiveness-checking const _exhaustive: never = key; // Just ignore any values that we don't know about break; } } } return state as LobbyConfigState; } export function ofNoop({ type }: LobbyConfigAction, state: LobbyConfigState) { return { ...state[type], }; } export function ofAny({ type, value }: LobbyConfigAction, state: LobbyConfigState) { return { ...state[type], currentValue: value, displayValue: value, warning: undefined, }; } export function ofBoolean( { type, value }: Extract, state: LobbyConfigState ) { if (value === undefined) { return { ...state[type], displayValue: value, warning: undefined, }; } else { return { ...state[type], currentValue: value, displayValue: value, warning: undefined, }; } } export function ofPositiveInteger( { type, value }: Extract, state: LobbyConfigState ) { if (value === undefined) { return { ...state[type], displayValue: value, warning: undefined, }; } if (typeof value !== 'number') { return { ...state[type], displayValue: value, warning: `Value must be a number`, }; } if (value < 0) { return { ...state[type], displayValue: value, warning: `Value must be a positive integer`, }; } if (value > SAFE_UPPER_BOUNDS) { return { ...state[type], displayValue: value, warning: `Value is too large`, }; } if (Math.floor(value) !== value) { return { ...state[type], displayValue: value, warning: `Value must be an integer`, }; } return { ...state[type], currentValue: value, displayValue: value, warning: undefined, }; } export function ofPositiveFloat( { type, value }: Extract, state: LobbyConfigState ) { if (value === undefined) { return { ...state[type], displayValue: value, warning: undefined, }; } if (typeof value !== 'number') { return { ...state[type], displayValue: value, warning: `Value must be a number`, }; } if (value < 0) { return { ...state[type], displayValue: value, warning: `Value must be a positive number`, }; } if (value > SAFE_UPPER_BOUNDS) { return { ...state[type], displayValue: value, warning: `Value is too large`, }; } return { ...state[type], currentValue: value, displayValue: value, warning: undefined, }; } export function ofPositivePercent( { type, value }: Extract, state: LobbyConfigState ) { if (value === undefined) { return { ...state[type], displayValue: value, warning: undefined, }; } if (typeof value !== 'number') { return { ...state[type], displayValue: value, warning: 'Value must be a number', }; } if (value < 0) { return { ...state[type], displayValue: value, warning: `Value must be a positive number`, }; } if (value !== 0 && value < 0.01) { return { ...state[type], displayValue: value, warning: `Value is too small`, }; } if (value > SAFE_UPPER_BOUNDS) { return { ...state[type], displayValue: value, warning: `Value is too large`, }; } return { currentValue: Math.floor(100 * value), displayValue: value, warning: undefined, }; } export function ofWorldRadiusMin( { type, value }: Extract, state: LobbyConfigState ) { if (value === undefined) { return { ...state[type], displayValue: value, warning: undefined, }; } if (typeof value !== 'number') { return { ...state[type], displayValue: value, warning: `Value must be a number`, }; } if (value < 0) { return { ...state[type], displayValue: value, warning: `Value must be positive`, }; } if (Math.floor(value) !== value) { return { ...state[type], displayValue: value, warning: 'Value must be an integer', }; } if (value < 1000) { return { ...state[type], displayValue: value, warning: `Worlds smaller than 1000 are unlikely to have spawnable area`, }; } if (value > SAFE_UPPER_BOUNDS) { return { ...state[type], displayValue: value, warning: `Worlds can't be initialized with that large of a minimum radius`, }; } return { ...state[type], currentValue: value, displayValue: value, warning: undefined, }; } export function ofPerlinLengthScale( { type, value }: Extract, state: LobbyConfigState ) { if (value === undefined) { return { ...state[type], displayValue: value, warning: undefined, }; } if (typeof value !== 'number') { return { ...state[type], displayValue: value, warning: 'Value must be a number', }; } return { ...state[type], currentValue: 2 ** value, displayValue: value, warning: undefined, }; } export function ofMaxNaturalPlanetLevel( { type, value }: Extract, state: LobbyConfigState ) { if (value === undefined) { return { ...state[type], displayValue: value, warning: undefined, }; } if (typeof value !== 'number') { return { ...state[type], displayValue: value, warning: `Value must be a number`, }; } if (value < 0) { return { ...state[type], displayValue: value, warning: `Value must be a positive integer`, }; } if (value > 9) { return { ...state[type], displayValue: value, warning: `Planets can't naturally be larger than Level 9`, }; } if (Math.floor(value) !== value) { return { ...state[type], displayValue: value, warning: 'Value must be an integer', }; } return { ...state[type], currentValue: value, displayValue: value, warning: undefined, }; } export function ofTimeFactorHundredths( { type, value }: Extract, state: LobbyConfigState ) { if (value === undefined) { return { ...state[type], displayValue: value, warning: undefined, }; } if (typeof value !== 'number') { return { ...state[type], displayValue: value, warning: 'Value must be a number', }; } if (value < 1) { return { ...state[type], displayValue: value, warning: `Value must be greater than 0`, }; } if (Math.floor(value) !== value) { return { ...state[type], displayValue: value, warning: 'Value must be an integer', }; } return { ...state[type], currentValue: Math.floor(100 * value), displayValue: value, warning: undefined, }; } export function ofPlanetRarity( { type, value }: Extract, state: LobbyConfigState ) { if (value === undefined) { return { ...state[type], displayValue: value, warning: undefined, }; } if (typeof value !== 'number') { return { ...state[type], displayValue: value, warning: `Value must be a number`, }; } if (value < 1) { return { ...state[type], displayValue: value, warning: `Value must be greater than 0`, }; } if (value > SAFE_UPPER_BOUNDS) { return { ...state[type], displayValue: value, warning: `Value is too large`, }; } if (Math.floor(value) !== value) { return { ...state[type], displayValue: value, warning: `Value must be an integer`, }; } return { ...state[type], currentValue: value, displayValue: value, warning: undefined, }; } export function ofSpawnRimArea( { type, value }: Extract, state: LobbyConfigState ) { if (value === undefined) { return { ...state[type], displayValue: value, warning: undefined, }; } if (typeof value !== 'number') { return { ...state[type], displayValue: value, warning: 'Value must be a number', }; } // The `0` value disables this feature if (value === 0) { return { ...state[type], currentValue: value, displayValue: value, warning: undefined, }; } const currentValue = Math.floor(Math.PI * value ** 2); if (currentValue < 1000) { return { ...state[type], displayValue: value, warning: 'Spawnable area must be larger', }; } // Using 1 billion instead of SAFE_UPPER_BOUNDS because math is done against this and don't want to lose precision if (currentValue > 1_000_000_000) { return { ...state[type], displayValue: value, warning: `Spawnable area is too large, instead use 0 to disable`, }; } return { ...state[type], currentValue, displayValue: value, warning: undefined, }; } export function ofSpaceJunkLimit( { type, value }: Extract, state: LobbyConfigState ) { if (value === undefined) { return { ...state[type], displayValue: value, warning: undefined, }; } if (typeof value !== 'number') { return { ...state[type], displayValue: value, warning: 'Value must be a number', }; } if (value < 1) { return { ...state[type], displayValue: value, warning: `Value must be greated than 0`, }; } if (value > SAFE_UPPER_BOUNDS) { return { ...state[type], displayValue: value, warning: `Value is too large`, }; } if (state.PLANET_LEVEL_JUNK.currentValue) { let warning; // Validate that every planet can be captured within SPACE_JUNK_LIMIT state.PLANET_LEVEL_JUNK.currentValue.forEach((junkAmount, planetLevel) => { if (value < junkAmount) { warning = `You'd be unable to capture Level ${planetLevel} planets with that Space Junk Limit`; } }); if (warning) { return { ...state[type], displayValue: value, warning, }; } } if (Math.floor(value) !== value) { return { ...state[type], displayValue: value, warning: `Value must be an integer`, }; } return { ...state[type], currentValue: value, displayValue: value, warning: undefined, }; } export function ofPlanetLevelJunk( { type, index, value }: Extract, state: LobbyConfigState ) { const prevCurrentValue = state[type].currentValue; const prevDisplayValue = state[type].displayValue; const spaceJunk = state.SPACE_JUNK_LIMIT.currentValue; if (!prevDisplayValue) { return { ...state[type], warning: `Failed to update ${type}`, }; } if (value === undefined) { return { ...state[type], warning: undefined, }; } const currentValue = [...prevCurrentValue]; const displayValue = [...prevDisplayValue]; displayValue[index] = value; if (typeof value !== 'number') { return { ...state[type], displayValue, warning: `Value must be a number`, }; } if (value < 0) { return { ...state[type], displayValue, warning: `Value must be positive`, }; } if (value > SAFE_UPPER_BOUNDS) { return { ...state[type], displayValue, warning: `Value is too large`, }; } if (spaceJunk < value) { return { currentValue, displayValue, warning: `Space junk on Level ${index} planets would exceed the Space Junk Limit`, }; } currentValue[index] = value; return { ...state[type], currentValue, displayValue, warning: undefined, }; } export function ofArtifactPointValues( { type, index, value }: Extract, state: LobbyConfigState ) { const prevCurrentValue = state[type].currentValue; const prevDisplayValue = state[type].displayValue; if (!prevDisplayValue) { return { ...state[type], warning: `Failed to update ${type}`, }; } if (value === undefined) { return { ...state[type], warning: undefined, }; } const currentValue = [...prevCurrentValue]; const displayValue = [...prevDisplayValue]; displayValue[index] = value; if (typeof value !== 'number') { return { ...state[type], displayValue, warning: `Value must be a number`, }; } if (value < 0) { return { ...state[type], displayValue, warning: `Value must be positive`, }; } if (value > SAFE_UPPER_BOUNDS) { return { ...state[type], displayValue, warning: `Value is too large`, }; } currentValue[index] = value; return { ...state[type], currentValue, displayValue, warning: undefined, }; } export function ofCaptureZoneRadius( { type, value }: Extract, state: LobbyConfigState ) { if (value === undefined) { return { ...state[type], displayValue: value, warning: undefined, }; } if (typeof value !== 'number') { return { ...state[type], displayValue: value, warning: `Value must be a number`, }; } if (value < 1) { return { ...state[type], displayValue: value, warning: `Value must be a greater than 0`, }; } if (value > SAFE_UPPER_BOUNDS) { return { ...state[type], displayValue: value, warning: `Value is too large`, }; } if (Math.floor(value) !== value) { return { ...state[type], displayValue: value, warning: `Value must be an integer`, }; } return { ...state[type], currentValue: value, displayValue: value, warning: undefined, }; } export function ofCaptureZonesPer5000WorldRadius( { type, value }: Extract, state: LobbyConfigState ) { if (value === undefined) { return { ...state[type], displayValue: value, warning: undefined, }; } if (typeof value !== 'number') { return { ...state[type], displayValue: value, warning: `Value must be a number`, }; } if (value < 1) { return { ...state[type], displayValue: value, warning: `Value must be a greater than 0`, }; } if (value > 10) { return { ...state[type], displayValue: value, warning: `Value is too large`, }; } if (Math.floor(value) !== value) { return { ...state[type], displayValue: value, warning: `Value must be an integer`, }; } return { ...state[type], currentValue: value, displayValue: value, warning: undefined, }; } export function ofCaptureZoneChangeBlockInterval( { type, value }: Extract, state: LobbyConfigState ) { if (value === undefined) { return { ...state[type], displayValue: value, warning: undefined, }; } if (typeof value !== 'number') { return { ...state[type], displayValue: value, warning: `Value must be a number`, }; } if (value < 1) { return { ...state[type], displayValue: value, warning: `Value must be a greater than 0`, }; } if (value > 255) { return { ...state[type], displayValue: value, warning: `Value is too large`, }; } if (Math.floor(value) !== value) { return { ...state[type], displayValue: value, warning: `Value must be an integer`, }; } return { ...state[type], currentValue: value, displayValue: value, warning: undefined, }; } export function ofCaptureZonePlanetLevelScore( { type, index, value }: Extract, state: LobbyConfigState ) { const prevCurrentValue = state[type].currentValue; const prevDisplayValue = state[type].displayValue; if (!prevDisplayValue) { return { ...state[type], warning: `Failed to update ${type}`, }; } if (value === undefined) { return { ...state[type], warning: undefined, }; } const currentValue = [...prevCurrentValue]; const displayValue = [...prevDisplayValue]; displayValue[index] = value; if (typeof value !== 'number') { return { ...state[type], displayValue, warning: `Value must be a number`, }; } if (value < 0) { return { ...state[type], displayValue, warning: `Value must be positive`, }; } if (value > SAFE_UPPER_BOUNDS) { return { ...state[type], displayValue, warning: `Value is too large`, }; } currentValue[index] = value; return { ...state[type], currentValue, displayValue, warning: undefined, }; } export function ofPlanetLevelThresholds( { type, index, value }: Extract, state: LobbyConfigState ) { const prevCurrentValue = state[type].currentValue; const prevDisplayValue = state[type].displayValue; const thresholds = state.PLANET_LEVEL_THRESHOLDS.currentValue; const prevIndex = index - 1; const prevThreshold = thresholds[prevIndex]; if (!prevDisplayValue) { return { ...state[type], warning: `Failed to update ${type}`, }; } if (value === undefined) { return { ...state[type], warning: undefined, }; } const currentValue = [...prevCurrentValue]; const displayValue = [...prevDisplayValue]; if (index === 0) { // Level 0 boundary always has to be this number displayValue[index] = 16777216; currentValue[index] = 16777216; return { ...state[type], displayValue, currentValue, warning: undefined, }; } displayValue[index] = value; if (typeof value !== 'number') { return { ...state[type], displayValue, warning: `Value must be a number`, }; } if (value < 1) { return { ...state[type], displayValue, warning: `Value must be greater than 0`, }; } if (value > 16777215) { return { ...state[type], displayValue, warning: `Value is too large`, }; } if (prevThreshold <= value) { return { currentValue, displayValue, warning: `Level ${index} planet threshold matches or exceeds previous threshold`, }; } currentValue[index] = value; return { ...state[type], currentValue, displayValue, warning: undefined, }; } ================================================ FILE: src/Frontend/Panes/Lobbies/SnarkPane.tsx ================================================ import React from 'react'; import { Checkbox, DarkForestCheckbox, DarkForestNumberInput, NumberInput, } from '../../Components/Input'; import { Row } from '../../Components/Row'; import { LobbiesPaneProps, Warning } from './LobbiesUtils'; export function SnarkPane({ config, onUpdate }: LobbiesPaneProps) { return ( <> ) => { onUpdate({ type: 'DISABLE_ZK_CHECKS', value: e.target.checked }); }} /> {config.DISABLE_ZK_CHECKS.warning} Planet hash key: ) => { onUpdate({ type: 'PLANETHASH_KEY', value: e.target.value }); }} /> {config.PLANETHASH_KEY.warning} Space type key: ) => { onUpdate({ type: 'SPACETYPE_KEY', value: e.target.value }); }} /> {config.SPACETYPE_KEY.warning} Biome base key: ) => { onUpdate({ type: 'BIOMEBASE_KEY', value: e.target.value }); }} /> {config.BIOMEBASE_KEY.warning} ); } ================================================ FILE: src/Frontend/Panes/Lobbies/SpaceJunkPane.tsx ================================================ import _ from 'lodash'; import React from 'react'; import { Checkbox, DarkForestCheckbox, DarkForestNumberInput, NumberInput, } from '../../Components/Input'; import { Row } from '../../Components/Row'; import { DarkForestSlider, Slider } from '../../Components/Slider'; import { LobbiesPaneProps, Warning } from './LobbiesUtils'; function JunkPerLevel({ value, index, onUpdate, }: LobbiesPaneProps & { index: number; value: number | undefined }) { return (
Level {index} ) => { onUpdate({ type: 'PLANET_LEVEL_JUNK', index, value: e.target.value }); }} />
); } const rowChunkSize = 5; const junkRowStyle = { gap: '8px' } as CSSStyleDeclaration & React.CSSProperties; export function SpaceJunkPane({ config, onUpdate }: LobbiesPaneProps) { let spaceJunkOptions = null; if (config.SPACE_JUNK_ENABLED.currentValue === true) { const junk = _.chunk(config.PLANET_LEVEL_JUNK.displayValue, rowChunkSize).map( (items, rowIdx) => { return ( {items.map((displayValue, idx) => ( ))} ); } ); spaceJunkOptions = ( <> Player space junk maximum ) => { onUpdate({ type: 'SPACE_JUNK_LIMIT', value: e.target.value }); }} /> {config.SPACE_JUNK_LIMIT.warning} ) => { onUpdate({ type: 'ABANDON_SPEED_CHANGE_PERCENT', value: e.target.value }); }} /> {config.ABANDON_SPEED_CHANGE_PERCENT.warning} ) => { onUpdate({ type: 'ABANDON_RANGE_CHANGE_PERCENT', value: e.target.value }); }} /> {config.ABANDON_RANGE_CHANGE_PERCENT.warning} Default junk for each planet level {junk} {config.PLANET_LEVEL_JUNK.warning} ); } return ( <> ) => { onUpdate({ type: 'SPACE_JUNK_ENABLED', value: e.target.checked }); }} /> {config.SPACE_JUNK_ENABLED.warning} {spaceJunkOptions} ); } ================================================ FILE: src/Frontend/Panes/Lobbies/SpaceTypeBiomePane.tsx ================================================ import React from 'react'; import { Checkbox, DarkForestCheckbox } from '../../Components/Input'; import { Row } from '../../Components/Row'; import { DarkForestSlider, DarkForestSliderHandle, Slider, SliderHandle, } from '../../Components/Slider'; import { LobbiesPaneProps, Warning } from './LobbiesUtils'; export function SpaceTypeBiomePane({ config, onUpdate }: LobbiesPaneProps) { return ( <> ) => onUpdate({ type: 'PERLIN_MIRROR_X', value: e.target.checked }) } /> {config.PERLIN_MIRROR_X.warning} ) => onUpdate({ type: 'PERLIN_MIRROR_Y', value: e.target.checked }) } /> {config.PERLIN_MIRROR_Y.warning} ) => onUpdate({ type: 'PERLIN_LENGTH_SCALE', value: e.target.value }) } /> {config.PERLIN_LENGTH_SCALE.warning} ) => { onUpdate({ type: 'PERLIN_THRESHOLD_1', value: e.target.value }); }} /> ) => { onUpdate({ type: 'PERLIN_THRESHOLD_2', value: e.target.value }); }} /> ) => { onUpdate({ type: 'PERLIN_THRESHOLD_3', value: e.target.value }); }} /> {config.PERLIN_THRESHOLD_1.warning || config.PERLIN_THRESHOLD_2.warning || config.PERLIN_THRESHOLD_3.warning} ) => { onUpdate({ type: 'BIOME_THRESHOLD_1', value: e.target.value }); }} /> ) => { onUpdate({ type: 'BIOME_THRESHOLD_2', value: e.target.value }); }} /> {config.BIOME_THRESHOLD_1.warning || config.BIOME_THRESHOLD_2.warning} ); } ================================================ FILE: src/Frontend/Panes/Lobbies/WorldSizePane.tsx ================================================ import React from 'react'; import { Checkbox, DarkForestCheckbox, DarkForestNumberInput, NumberInput, } from '../../Components/Input'; import { Row } from '../../Components/Row'; import { LobbiesPaneProps, Warning } from './LobbiesUtils'; export function WorldSizePane({ config, onUpdate }: LobbiesPaneProps) { return ( <> ) => onUpdate({ type: 'WORLD_RADIUS_LOCKED', value: e.target.checked }) } /> {config.WORLD_RADIUS_LOCKED.warning} {config.WORLD_RADIUS_LOCKED ? 'World radius:' : 'Minimum world radius'} ) => { onUpdate({ type: 'WORLD_RADIUS_MIN', value: e.target.value }); }} /> {config.WORLD_RADIUS_MIN.warning} ); } ================================================ FILE: src/Frontend/Panes/Lobbies/minimap.worker.ts ================================================ import { perlin } from '@darkforest_eth/hashing'; import { SpaceType, WorldCoords } from '@darkforest_eth/types'; import { DrawMessage, MinimapConfig } from './MinimapUtils'; const ctx = self as unknown as Worker; function spaceTypePerlin(coords: WorldCoords, config: MinimapConfig): number { return perlin(coords, { ...config, floor: true }); } function spaceTypeFromPerlin(perlin: number, config: MinimapConfig): SpaceType { if (perlin < config.perlinThreshold1) { return SpaceType.NEBULA; } else if (perlin < config.perlinThreshold2) { return SpaceType.SPACE; } else if (perlin < config.perlinThreshold3) { return SpaceType.DEEP_SPACE; } else { return SpaceType.DEAD_SPACE; } } // Initial implementation by @nicholashc (https://github.com/nicholashc) // https://github.com/darkforest-eth/plugins/blob/358a386356b9145005f17045d9f4ce22661d99a1/content/utilities/mini-map/plugin.js function generate(config: MinimapConfig): DrawMessage { const data = []; const step = config.worldRadius / 25; const radius = config.worldRadius; // utility functions const checkBounds = (a: number, b: number, x: number, y: number, r: number) => { const dist = (a - x) * (a - x) + (b - y) * (b - y); r *= r; if (dist < r) { return true; } return false; }; // generate x coordinates for (let i = radius * -1; i < radius; i += step) { // generate y coordinates for (let j = radius * -1; j < radius; j += step) { // filter points within map circle if (checkBounds(0, 0, i, j, radius)) { // store coordinate and space type data.push({ x: i, y: j, type: spaceTypeFromPerlin(spaceTypePerlin({ x: i, y: j }, config), config), }); } } } return { radius, data }; } ctx.addEventListener('message', (e: MessageEvent) => { if (e.data) { const msg = generate(JSON.parse(e.data)); ctx.postMessage(JSON.stringify(msg)); } }); ================================================ FILE: src/Frontend/Panes/ManagePlanetArtifacts/ArtifactActions.tsx ================================================ import { canActivateArtifact, canDepositArtifact, canWithdrawArtifact, durationUntilArtifactAvailable, isActivated, isLocatable, } from '@darkforest_eth/gamelogic'; import { isUnconfirmedActivateArtifactTx, isUnconfirmedDeactivateArtifactTx, isUnconfirmedDepositArtifactTx, isUnconfirmedWithdrawArtifactTx, } from '@darkforest_eth/serde'; import { Artifact, ArtifactId, ArtifactType, LocationId, TooltipName } from '@darkforest_eth/types'; import React, { useCallback } from 'react'; import { Btn } from '../../Components/Btn'; import { Spacer } from '../../Components/CoreUI'; import { ArtifactRarityLabelAnim } from '../../Components/Labels/ArtifactLabels'; import { LoadingSpinner } from '../../Components/LoadingSpinner'; import { Sub } from '../../Components/Text'; import { formatDuration } from '../../Components/TimeUntil'; import { useAccount, useArtifact, usePlanet, usePlanetArtifacts, useUIManager, } from '../../Utils/AppHooks'; import { TooltipTrigger, TooltipTriggerProps } from '../Tooltip'; export function ArtifactActions({ artifactId, depositOn, }: { artifactId: ArtifactId; depositOn?: LocationId; }) { const uiManager = useUIManager(); const account = useAccount(uiManager); const artifactWrapper = useArtifact(uiManager, artifactId); const artifact = artifactWrapper.value; const depositPlanetWrapper = usePlanet(uiManager, depositOn); const onPlanetWrapper = usePlanet(uiManager, artifact?.onPlanetId); const depositPlanet = depositPlanetWrapper.value; const onPlanet = onPlanetWrapper.value; const otherArtifactsOnPlanet = usePlanetArtifacts(onPlanetWrapper, uiManager); const withdraw = useCallback( (artifact: Artifact) => { onPlanet && uiManager.withdrawArtifact(onPlanet.locationId, artifact?.id); }, [onPlanet, uiManager] ); const deposit = useCallback( (artifact: Artifact) => { artifact && depositPlanetWrapper.value && uiManager.depositArtifact(depositPlanetWrapper.value.locationId, artifact?.id); }, [uiManager, depositPlanetWrapper.value] ); const activate = useCallback( async (artifact: Artifact) => { if (onPlanet && isLocatable(onPlanet)) { let targetPlanetId = undefined; if (artifact.artifactType === ArtifactType.Wormhole) { const targetPlanet = await uiManager.startWormholeFrom(onPlanet); targetPlanetId = targetPlanet?.locationId; } uiManager.activateArtifact(onPlanet.locationId, artifact.id, targetPlanetId); } }, [onPlanet, uiManager] ); const deactivate = useCallback( (artifact: Artifact) => { onPlanet && uiManager.deactivateArtifact(onPlanet.locationId, artifact.id); }, [onPlanet, uiManager] ); if (!artifact || (!onPlanet && !depositPlanet) || !account) return null; const actions: TooltipTriggerProps[] = []; const withdrawing = artifact.transactions?.hasTransaction(isUnconfirmedWithdrawArtifactTx); const depositing = artifact.transactions?.hasTransaction(isUnconfirmedDepositArtifactTx); const activating = artifact.transactions?.hasTransaction(isUnconfirmedActivateArtifactTx); const deactivating = artifact.transactions?.hasTransaction(isUnconfirmedDeactivateArtifactTx); const canHandleDeposit = depositPlanetWrapper.value && depositPlanetWrapper.value.planetLevel > artifact.rarity; const canHandleWithdraw = onPlanetWrapper.value && onPlanetWrapper.value.planetLevel > artifact.rarity; const wait = durationUntilArtifactAvailable(artifact); if (canDepositArtifact(account, artifact, depositPlanetWrapper.value)) { actions.unshift({ name: TooltipName.DepositArtifact, extraContent: !canHandleDeposit && ( <> . {` artifacts can only be deposited on level ${artifact.rarity + 1}+ spacetime rips`} ), children: ( { e.stopPropagation(); canHandleDeposit && deposit(artifact); }} > {depositing ? : 'Deposit'} ), }); } if (isActivated(artifact) && artifact.artifactType !== ArtifactType.BlackDomain) { actions.unshift({ name: TooltipName.DeactivateArtifact, children: ( { e.stopPropagation(); deactivate(artifact); }} > {deactivating ? : 'Deactivate'} ), }); } if (canWithdrawArtifact(account, artifact, onPlanet)) { actions.unshift({ name: TooltipName.WithdrawArtifact, extraContent: !canHandleWithdraw && ( <> . {` artifacts can only be withdrawn from level ${artifact.rarity + 1}+ spacetime rips`} ), children: ( { e.stopPropagation(); canHandleWithdraw && withdraw(artifact); }} > {withdrawing ? : 'Withdraw'} ), }); } if (canActivateArtifact(artifact, onPlanet, otherArtifactsOnPlanet)) { actions.unshift({ name: TooltipName.ActivateArtifact, children: ( { e.stopPropagation(); activate(artifact); }} > {activating ? : 'Activate'} ), }); } if (wait > 0) { actions.unshift({ name: TooltipName.Empty, extraContent: <>You have to wait before activating an artifact again, children: {formatDuration(wait)}, }); } return (
{actions.length > 0 && } {actions.map((a, i) => ( ))}
); } ================================================ FILE: src/Frontend/Panes/ManagePlanetArtifacts/ManageArtifacts.tsx ================================================ import { Artifact, LocatablePlanet, PlanetType } from '@darkforest_eth/types'; import React, { useEffect, useState } from 'react'; import styled, { css } from 'styled-components'; import { Spacer } from '../../Components/CoreUI'; import { ModalHandle } from '../../Views/ModalPane'; import { AllArtifacts } from '../ArtifactsList'; export function ManageArtifactsPane({ planet, artifactsInWallet, artifactsOnPlanet, playerAddress, modal, }: { planet: LocatablePlanet; artifactsInWallet: Artifact[]; artifactsOnPlanet: Array; playerAddress: string; modal: ModalHandle; }) { const isMyTradingPost = planet.owner === playerAddress && planet.planetType === PlanetType.TRADING_POST && !planet.destroyed; const [viewingDepositList, setViewingDepositList] = useState(false); let action; useEffect(() => { setViewingDepositList(false); }, [planet.locationId, playerAddress]); return ( <> !!a ) as Artifact[] } modal={modal} noArtifactsMessage={ <> No Artifacts
On This Planet } noShipsMessage={ <> No Ships
On This Planet } /> {action && ( <> {action} )} {isMyTradingPost && ( { setViewingDepositList(false); }} > On This Planet { setViewingDepositList(true); }} > Deposit Artifact )} ); } const SelectArtifactsContainer = styled.div` padding: 4px; display: flex; justify-content: space-between; align-items: center; flex-direction: row; `; const SelectArtifactList = styled.span` ${({ selected }: { selected?: boolean }) => css` ${selected && 'text-decoration: underline;'} cursor: pointer; `} `; ================================================ FILE: src/Frontend/Panes/ManagePlanetArtifacts/ManagePlanetArtifactsPane.tsx ================================================ import { isLocatable } from '@darkforest_eth/gamelogic'; import { LocationId } from '@darkforest_eth/types'; import React from 'react'; import { CenterBackgroundSubtext, Underline } from '../../Components/CoreUI'; import { useAccount, useMyArtifactsList, usePlanet, useUIManager } from '../../Utils/AppHooks'; import { useEmitterValue } from '../../Utils/EmitterHooks'; import { ModalHandle } from '../../Views/ModalPane'; import { ManageArtifactsPane } from './ManageArtifacts'; export function PlanetInfoHelpContent() { return (

Metadata related to this planet.

); } export function ManagePlanetArtifactsHelpContent() { return (

Using this pane, you can manage the artifacts that are on this planet specifically. You can activate a single artifact at a time. Some artifacts have a cooldown period after deactivating during which they can not be activated.


If your planet is a Spacetime Rip, you can also withdraw and deposit artifacts. When you withdraw an artifact, it is transferred to your address as an ERC 721 token.

); } /** * This is the place where a user can manage all of their artifacts on a * particular planet. This includes prospecting, withdrawing, depositing, * activating, and deactivating artifacts. */ export function ManagePlanetArtifactsPane({ initialPlanetId, modal, }: { initialPlanetId: LocationId | undefined; modal: ModalHandle; }) { const uiManager = useUIManager(); const account = useAccount(uiManager); const planetId = useEmitterValue(uiManager.selectedPlanetId$, initialPlanetId); const planet = usePlanet(uiManager, planetId).value; const myArtifacts = useMyArtifactsList(uiManager); const onPlanet = uiManager.getArtifactsWithIds(planet?.heldArtifactIds || []); const artifactsInWallet = []; for (const a of myArtifacts) { if (!a.onPlanetId) { artifactsInWallet.push(a); } } if (planet && myArtifacts && isLocatable(planet) && account) { return ( ); } else { return ( Select a Planet ); } } ================================================ FILE: src/Frontend/Panes/ManagePlanetArtifacts/SortBy.tsx ================================================ import { TooltipName, Upgrade } from '@darkforest_eth/types'; import React from 'react'; import styled from 'styled-components'; import { CenterRow, Spacer } from '../../Components/CoreUI'; import { Icon, IconType } from '../../Components/Icons'; import dfstyles from '../../Styles/dfstyles'; import { TooltipTrigger } from '../Tooltip'; type IconConfig = { iconType: IconType; key: keyof Upgrade; tooltip: TooltipName; }; const icons: readonly IconConfig[] = [ { iconType: IconType.Defense, key: 'defMultiplier', tooltip: TooltipName.DefenseMultiplier, }, { iconType: IconType.Energy, key: 'energyCapMultiplier', tooltip: TooltipName.EnergyCapMultiplier, }, { iconType: IconType.EnergyGrowth, key: 'energyGroMultiplier', tooltip: TooltipName.EnergyGrowthMultiplier, }, { iconType: IconType.Range, key: 'rangeMultiplier', tooltip: TooltipName.RangeMultiplier, }, { iconType: IconType.Speed, key: 'speedMultiplier', tooltip: TooltipName.SpeedMultiplier, }, ] as const; export function SortBy({ sortBy, setSortBy, }: { sortBy: keyof Upgrade | undefined; setSortBy: (k: keyof Upgrade | undefined) => void; }) { return ( Sort By: {icons.map(({ key, tooltip, iconType }) => ( { if (key === sortBy) { setSortBy(undefined); } else { setSortBy(key); } }} iconColor={key === sortBy ? dfstyles.colors.dfgreen : dfstyles.colors.subtext} > ))} ); } const SortByIconContainer = styled.div<{ iconColor: string }>` line-height: 0; padding-right: 8px; /* Set the Icon color if specified on the outer component */ --df-icon-color: ${({ iconColor }) => iconColor}; `; ================================================ FILE: src/Frontend/Panes/ManagePlanetArtifacts/UpgradeStatsView.tsx ================================================ import { ArtifactType, Upgrade } from '@darkforest_eth/types'; import React from 'react'; import styled from 'styled-components'; import { EmSpacer, Spacer } from '../../Components/CoreUI'; import { Icon, IconType } from '../../Components/Icons'; import dfstyles from '../../Styles/dfstyles'; const { dfgreen, dfred, subtext } = dfstyles.colors; function upgradeValue(value: number) { const normalizedValue = Math.floor(value - 100); if (normalizedValue >= -0) { return '+' + normalizedValue + '%'; } else { return normalizedValue + '%'; } } function SingleUpgrade({ upgrade, active, icon, getMultiplier, }: { upgrade: Upgrade; active: boolean; icon: IconType; getMultiplier: (u: Upgrade) => number; }) { const mult = getMultiplier(upgrade); const activeColor = mult > 100 ? dfgreen : mult < 100 ? dfred : subtext; const color = active ? activeColor : subtext; return ( {upgradeValue(getMultiplier(upgrade))} ); } const getDefenseMult = (u: Upgrade) => u.defMultiplier; const getEnergyCapMult = (u: Upgrade) => u.energyCapMultiplier; const getEnergyGroMult = (u: Upgrade) => u.energyGroMultiplier; const getRangeMult = (u: Upgrade) => u.rangeMultiplier; const getSpeedMult = (u: Upgrade) => u.speedMultiplier; export function UpgradeStatsView({ upgrade, isActive, artifactType, }: { upgrade: Upgrade; isActive: boolean; artifactType: ArtifactType; }) { let specialDescription; switch (artifactType) { case ArtifactType.BlackDomain: specialDescription = 'locks planet'; break; case ArtifactType.Wormhole: specialDescription = 'decreases travel time'; break; case ArtifactType.BloomFilter: specialDescription = "refills planet's energy and silver"; break; case ArtifactType.PhotoidCannon: specialDescription = 'single use sniper'; break; } if (specialDescription) { return ( {specialDescription} ); } const iconProps = { active: isActive, upgrade }; return ( ); } const StyledSingleUpgrade = styled.div<{ color?: string; iconColor?: string }>` display: inline-flex; justify-content: flex-start; align-items: center; width: 45px; color: ${({ color }) => color}; /* Set the Icon color if specified on the outer component */ --df-icon-color: ${({ iconColor }) => iconColor}; `; const UpgradeSummaryContainer = styled.div` color: ${dfstyles.colors.subtext}; display: inline-block; font-size: 12px; justify-content: space-between; align-items: center; `; ================================================ FILE: src/Frontend/Panes/OnboardingPane.tsx ================================================ import { ModalName } from '@darkforest_eth/types'; import React, { useEffect, useState } from 'react'; import styled from 'styled-components'; import { Btn } from '../Components/Btn'; import { Icon, IconType } from '../Components/Icons'; import { Green, Red, White } from '../Components/Text'; import { TextPreview } from '../Components/TextPreview'; import dfstyles from '../Styles/dfstyles'; import { useAccount, useUIManager } from '../Utils/AppHooks'; import { ModalPane } from '../Views/ModalPane'; const StyledOnboardingContent = styled.div` width: 36em; height: 32em; position: relative; color: ${dfstyles.colors.text}; .btn { position: absolute; right: 0.5em; bottom: 0.5em; } .indent { margin-left: 1em; } & > p, & > div { margin: 1em 0; } & > div { display: flex; flex-direction: row; justify-content: space-between; } `; const enum OnboardState { Money, Storage, Keys, Help, Finished, } function OnboardMoney({ advance }: { advance: () => void }) { const uiManager = useUIManager(); const account = useAccount(uiManager); const explorerAddressLink = `https://blockscout.com/poa/xdai/address/${account}`; return (

Welcome to Dark Forest!

There is real money being transacted in-game! We have initialized a{' '} window.open('https://github.com/austintgriffith/burner-wallet')}> burner wallet {' '} for you and dripped 15c to it, courtesy of Dark Forest Team and xDAI.

Your burner wallet address is:
window.open(explorerAddressLink)}>{account}

This means that when you make moves on Dark Forest,{' '} you are authorizing the client to pay gas fees on your behalf.

To ensure the safety of your balance, we require you to enable popups so that all transactions may be confirmed by you. Note that you can disable popups for small transactions in settings.

Make sure you understand all of the above before proceeding.

I understand, please proceed.
); } function OnboardStorage({ advance }: { advance: () => void }) { return (

The game stores important information like your private key,{' '} home coordinates, and map data in your browser's local storage / cache. If you clear your browser history, you risk losing your data!

Your private key and home coordinates act as your password. You can use them to access your Dark Forest account on other browsers, or to continue playing if you accidentally clear local storage. But this also means{' '} they should never be viewed by anyone else!

Make sure you back them up and keep them somewhere safe.

On the next page, you will be able to view and copy your private key and home coordinates.{' '} When you are ready to back them up, please proceed.

Proceed
); } function OnboardKeys({ advance }: { advance: () => void }) { const uiManager = useUIManager(); const [sKey, setSKey] = useState(undefined); useEffect(() => { if (!uiManager) return; setSKey(uiManager.getPrivateKey()); }, [uiManager]); const [home, setHome] = useState(undefined); useEffect(() => { if (!uiManager) return; const coords = uiManager.getHomeCoords(); setHome(coords ? `(${coords.x}, ${coords.y})` : ''); }, [uiManager]); return (

Your private key is:

Your home coordinates are:
{home}

When you have backed up your key and coordinates, please proceed.

Proceed
); } function OnboardHelp({ advance }: { advance: () => void }) { return (

For an overview of how to play, rules, and scoring, click the question mark icon on the left to open the Help Pane.

Proceed
); } function OnboardFinished({ advance }: { advance: () => void }) { return (

That's all! You're now ready to play the game!

We invite you to log into the universe. Click Proceed to join the world of{' '} DARK FOREST...

Proceed
); } export default function OnboardingPane({ visible, onClose, }: { visible: boolean; onClose: () => void; }) { const [onboardState, setOnboardState] = useState(OnboardState.Money); const advance = () => setOnboardState((x) => x + 1); useEffect(() => { if (onboardState === OnboardState.Finished + 1) { onClose(); } }, [onboardState, onClose]); return ( {onboardState === OnboardState.Money && } {onboardState === OnboardState.Storage && } {onboardState === OnboardState.Keys && } {onboardState === OnboardState.Help && } {onboardState === OnboardState.Finished && } ); } ================================================ FILE: src/Frontend/Panes/PlanetContextPane.tsx ================================================ import { ModalName, Planet, PlanetType } from '@darkforest_eth/types'; import React, { useCallback, useEffect, useMemo } from 'react'; import GameUIManager from '../../Backend/GameLogic/GameUIManager'; import { Wrapper } from '../../Backend/Utils/Wrapper'; import { CapturePlanetButton } from '../Components/CapturePlanetButton'; import { VerticalSplit } from '../Components/CoreUI'; import { MineArtifactButton } from '../Components/MineArtifactButton'; import { OpenBroadcastPaneButton, OpenHatPaneButton, OpenManagePlanetArtifactsButton, OpenPlanetInfoButton, OpenUpgradeDetailsPaneButton, } from '../Components/OpenPaneButtons'; import { snips } from '../Styles/dfstyles'; import { useAccount, useSelectedPlanet, useUIManager } from '../Utils/AppHooks'; import { useEmitterSubscribe } from '../Utils/EmitterHooks'; import { useOnUp } from '../Utils/KeyEmitters'; import { EXIT_PANE, TOGGLE_ABANDON, TOGGLE_SEND } from '../Utils/ShortcutConstants'; import UIEmitter, { UIEmitterEvent } from '../Utils/UIEmitter'; import { ModalHandle, ModalPane } from '../Views/ModalPane'; import { PlanetCard, PlanetCardTitle } from '../Views/PlanetCard'; import { getNotifsForPlanet, PlanetNotifications } from '../Views/PlanetNotifications'; import { SendResources } from '../Views/SendResources'; import { WithdrawSilver } from '../Views/WithdrawSilver'; function PlanetContextPaneContent({ modal, planet, uiManager, onToggleSendForces, onToggleAbandon, }: { modal: ModalHandle; planet: Wrapper; uiManager: GameUIManager; onToggleSendForces: () => void; onToggleAbandon: () => void; }) { const account = useAccount(uiManager); const notifs = useMemo(() => getNotifsForPlanet(planet.value, account), [planet, account]); const owned = planet.value?.owner === account; useEffect(() => { if (!planet.value) modal.popAll(); }, [planet.value, modal]); const p = planet.value; let captureRow = null; if (!p?.destroyed && uiManager.captureZonesEnabled) { captureRow = ; } let upgradeRow = null; if (!p?.destroyed && owned) { upgradeRow = ; } let hatRow = null; if (!p?.destroyed && owned) { hatRow = ; } let withdrawRow = null; if (!p?.destroyed && owned && p?.planetType === PlanetType.TRADING_POST) { withdrawRow = ; } let notifRow = null; if (!p?.destroyed && notifs.length > 0) { notifRow = ; } return ( <> {captureRow} <> {upgradeRow} <> {hatRow} {withdrawRow} {notifRow} ); } export function SelectedPlanetHelpContent() { return (

This pane allows you to interact with the currently selected planet. Pressing the ESCAPE key allows you to deselect the current planet.

); } export function PlanetContextPane({ visible, onClose }: { visible: boolean; onClose: () => void }) { const uiManager = useUIManager(); const planet = useSelectedPlanet(uiManager); /* All of this is done to support using the key commands on subpanes of the PlanetContextPane */ const doSend = useCallback(() => { if (!uiManager) return; const uiEmitter = UIEmitter.getInstance(); if (uiManager.isSendingForces() || uiManager.isSendingShip() || uiManager.isAbandoning()) { uiEmitter.emit(UIEmitterEvent.SendInitiated, planet.value); } else { uiEmitter.emit(UIEmitterEvent.SendCancelled); } }, [planet, uiManager]); const toggleSendingForces = useCallback(() => { if (planet.value?.destroyed && !uiManager.isSendingShip(planet.value?.locationId)) return; const isAbandoning = uiManager.isAbandoning(); if (isAbandoning) return; const isSending = uiManager.isSendingForces(); uiManager.setSending(!isSending); doSend(); }, [uiManager, doSend, planet]); const toggleAbandoning = useCallback(() => { if (planet.value?.destroyed) return; const isAbandoning = uiManager.isAbandoning(); uiManager.setAbandoning(!isAbandoning); doSend(); }, [uiManager, doSend, planet]); useOnUp( TOGGLE_SEND, () => { toggleSendingForces(); }, [toggleSendingForces] ); useOnUp( TOGGLE_ABANDON, () => { toggleAbandoning(); }, [toggleAbandoning] ); useOnUp( EXIT_PANE, () => { // If we clear the selectedPlanetId, the below hook will cancel and cleanup the sending uiManager.setSelectedPlanet(undefined); }, [uiManager] ); // If the locationId changes, cancel any sending useEmitterSubscribe( uiManager.selectedPlanetId$, () => { const uiEmitter = UIEmitter.getInstance(); uiEmitter.emit(UIEmitterEvent.SendCancelled); // Get the previous planet and clear it's artifact // so it doesn't have a ship selected the next time the planet is selected const previousPlanet = uiManager.getPreviousSelectedPlanet(); if (previousPlanet) { uiManager.setArtifactSending(previousPlanet.locationId, undefined); } }, [uiManager] ); const render = useCallback( (modal: ModalHandle) => ( ), [uiManager, planet, toggleSendingForces, toggleAbandoning] ); return ( } hideClose helpContent={SelectedPlanetHelpContent} width='350px' > {render} ); } ================================================ FILE: src/Frontend/Panes/PlanetDexPane.tsx ================================================ import { RECOMMENDED_MODAL_WIDTH } from '@darkforest_eth/constants'; import { formatNumber } from '@darkforest_eth/gamelogic'; import { getPlanetClass, getPlanetCosmetic, getPlanetName, rgbStr, } from '@darkforest_eth/procedural'; import { engineConsts } from '@darkforest_eth/renderer'; import { ModalName, Planet, PlanetType, RGBVec } from '@darkforest_eth/types'; import React, { useEffect, useState } from 'react'; import styled from 'styled-components'; import { getPlanetRank } from '../../Backend/Utils/Utils'; import { CenterBackgroundSubtext, Spacer } from '../Components/CoreUI'; import { Icon, IconType } from '../Components/Icons'; import { Sub } from '../Components/Text'; import { useUIManager } from '../Utils/AppHooks'; import { ModalPane } from '../Views/ModalPane'; import { PlanetLink } from '../Views/PlanetLink'; import { SortableTable } from '../Views/SortableTable'; const StyledPlanetThumb = styled.div<{ iconColor?: string }>` width: 20px; height: 20px; position: relative; line-height: 0; z-index: 1; /* Set the Icon color if specified on the outer component */ --df-icon-color: ${({ iconColor }) => iconColor}; `; const PlanetElement = styled.div` position: absolute; width: 100%; height: 100%; top: 0; left: 0; display: flex; justify-content: center; align-items: center; `; const PlanetName = styled.span` display: block; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 100px; `; const TableContainer = styled.div` overflow-y: scroll; `; export function PlanetThumb({ planet }: { planet: Planet }) { const radius = 5 + 2 * planet.planetLevel; // const radius = 5 + 3 * PlanetLevel.MAX; const { speed, range, defense } = engineConsts.colors.belt; const { baseStr } = getPlanetCosmetic(planet); const ringColor = (): string => { const myClass = getPlanetClass(planet); const myColor: RGBVec = [defense, range, speed][myClass]; return rgbStr(myColor); }; const ringW = radius * 1.5; const ringH = Math.max(2, ringW / 7); if (planet.planetType === PlanetType.SILVER_MINE) { return ( ); } else if (planet.planetType === PlanetType.SILVER_BANK) { return ( ); } else if (planet.planetType === PlanetType.RUINS) { return ( ); } else if (planet.planetType === PlanetType.TRADING_POST) { return ( ); } return (
0 ? ringColor() : 'none', }} /> ); } function HelpContent() { return (

These are all the planets you currently own.

The table is interactive, and allows you to sort the planets by clicking each column's header. You can also navigate to a planet that you own by clicking on its name. The planet you click will be centered at the spot on the screen where the current planet you have selected is located.

); } export function PlanetDexPane({ visible, onClose }: { visible: boolean; onClose: () => void }) { const uiManager = useUIManager(); const [planets, setPlanets] = useState([]); // update planet list on open / close useEffect(() => { if (!uiManager) return; const myAddr = uiManager.getAccount(); if (!myAddr) return; const ownedPlanets = uiManager.getAllOwnedPlanets().filter((planet) => planet.owner === myAddr); setPlanets(ownedPlanets); }, [visible, uiManager]); // refresh planets every 10 seconds useEffect(() => { if (!uiManager) return; if (!visible) return; const refreshPlanets = () => { if (!uiManager) return; const myAddr = uiManager.getAccount(); if (!myAddr) return; const ownedPlanets = uiManager .getAllOwnedPlanets() .filter((planet) => planet.owner === myAddr); setPlanets(ownedPlanets); }; const intervalId = setInterval(refreshPlanets, 10000); return () => { clearInterval(intervalId); }; }, [visible, uiManager]); const headers = ['', 'Planet Name', 'Level', 'Energy', 'Silver', 'Inventory']; const alignments: Array<'r' | 'c' | 'l'> = ['r', 'l', 'r', 'r', 'r', 'r']; const columns = [ (planet: Planet) => , (planet: Planet) => ( {getPlanetName(planet)} ), (planet: Planet) => {planet.planetLevel}, (planet: Planet) => {formatNumber(planet.energy)}, (planet: Planet) => {formatNumber(planet.silver)}, (planet: Planet) => {formatNumber(planet.heldArtifactIds.length)}, ]; const sortingFunctions = [ // thumb (_a: Planet, _b: Planet): number => 0, // name (a: Planet, b: Planet): number => { const [nameA, nameB] = [getPlanetName(a), getPlanetName(b)]; return nameA.localeCompare(nameB); }, // level (a: Planet, b: Planet): number => b.planetLevel - a.planetLevel, // energy (a: Planet, b: Planet): number => b.energy - a.energy, // silver (a: Planet, b: Planet): number => b.silver - a.silver, // artifacts (a: Planet, b: Planet): number => { const [numArtifacts, scoreB] = [a.heldArtifactIds.length, b.heldArtifactIds.length]; return scoreB - numArtifacts; }, ]; let content; if (planets.length === 0) { content = ( Loading Your Home Planet... ); } else { content = ( ); } return ( {content} ); } ================================================ FILE: src/Frontend/Panes/PlanetInfoPane.tsx ================================================ import { isLocatable } from '@darkforest_eth/gamelogic'; import { LocationId, TooltipName } from '@darkforest_eth/types'; import React from 'react'; import { CenterBackgroundSubtext } from '../Components/CoreUI'; import { AccountLabel } from '../Components/Labels/Labels'; import { TextPreview } from '../Components/TextPreview'; import dfstyles from '../Styles/dfstyles'; import { usePlanet, useUIManager } from '../Utils/AppHooks'; import { useEmitterValue } from '../Utils/EmitterHooks'; import { TooltipTrigger } from './Tooltip'; /** * This pane contains misc info about the planet, which does not have a place in the main Planet Context Pane. */ export function PlanetInfoPane({ initialPlanetId }: { initialPlanetId: LocationId | undefined }) { const uiManager = useUIManager(); const planetId = useEmitterValue(uiManager.selectedPlanetId$, initialPlanetId); const planet = usePlanet(uiManager, planetId).value; if (!isLocatable(planet)) { return ( planet with
unknown location
); } else { return ( <> id}>
coords}>
owner}> ); } } ================================================ FILE: src/Frontend/Panes/PlayerArtifactsPane.tsx ================================================ import { RECOMMENDED_MODAL_WIDTH } from '@darkforest_eth/constants'; import { ModalName } from '@darkforest_eth/types'; import React from 'react'; import { Spacer } from '../Components/CoreUI'; import { useMyArtifactsList, useUIManager } from '../Utils/AppHooks'; import { ModalHandle, ModalPane } from '../Views/ModalPane'; import { AllArtifacts } from './ArtifactsList'; function HelpContent() { return (

These are all the artifacts you currently own.

The table is interactive, and allows you to sort the artifacts by clicking each column's header. You can also view more information about a particular artifact by clicking on its name.

); } export function PlayerArtifactsPane({ visible, onClose, }: { visible: boolean; onClose: () => void; }) { const uiManager = useUIManager(); const artifacts = useMyArtifactsList(uiManager); const render = (handle: ModalHandle) => ; return ( {render} ); } ================================================ FILE: src/Frontend/Panes/PluginEditorPane.tsx ================================================ import { PluginId } from '@darkforest_eth/types'; import * as Prism from 'prismjs'; import * as React from 'react'; import { useState } from 'react'; import Editor from 'react-simple-code-editor'; import styled from 'styled-components'; import { PluginManager } from '../../Backend/GameLogic/PluginManager'; import { PLUGIN_TEMPLATE } from '../../Backend/Plugins/PluginTemplate'; import { Btn } from '../Components/Btn'; import { Spacer } from '../Components/CoreUI'; import { DarkForestTextInput, TextInput } from '../Components/Input'; import dfstyles from '../Styles/dfstyles'; require('prismjs/themes/prism-dark.css'); /** * Make sure the editor scrolls, and is always the same size. */ const EditorContainer = styled.div` overflow-y: scroll; border: 1px solid ${dfstyles.colors.borderDark}; border-radius: ${dfstyles.borderRadius}; width: 500px; height: 500px; .df-editor { width: 100%; min-height: 100%; } `; /** * Component for editing plugins. Saving causes its containing modal * to be closed, and the `overwrite` to be called, indicating that the * given plugin's source should be overwritten and reloaded. If no * plugin id is provided, assumes we're editing a new plugin. */ export function PluginEditorPane({ pluginHost, pluginId, setIsOpen, overwrite, }: { pluginHost?: PluginManager | null; pluginId?: PluginId; setIsOpen: (open: boolean) => void; overwrite: (newPluginName: string, newPluginCode: string, pluginId?: PluginId) => void; }) { const plugin = pluginId ? pluginHost?.getPluginFromLibrary(pluginId) : undefined; const [name, setName] = useState(plugin?.name); const [code, setCode] = useState(plugin?.code || PLUGIN_TEMPLATE); function onSaveClick() { overwrite(name || 'Unnamed', code || '', pluginId); setIsOpen(false); } function onNameInputChange(e: Event & React.ChangeEvent) { setName(e.target.value); } return ( <> Prism.highlight(code, Prism.languages.javascript, 'javascript')} padding={10} style={{ fontFamily: '"Fira code", "Fira Mono", monospace', fontSize: 12, }} onKeyDown={(e) => e.stopPropagation()} onKeyUp={(e) => e.stopPropagation()} /> Save ); } ================================================ FILE: src/Frontend/Panes/PluginLibraryPane.tsx ================================================ import { RECOMMENDED_MODAL_WIDTH } from '@darkforest_eth/constants'; import { ModalName, PluginId, Setting } from '@darkforest_eth/types'; import React, { useEffect, useState } from 'react'; import { ReactSortable } from 'react-sortablejs'; import styled from 'styled-components'; import { v4 as uuidv4 } from 'uuid'; import GameUIManager from '../../Backend/GameLogic/GameUIManager'; import { SerializedPlugin } from '../../Backend/Plugins/SerializedPlugin'; import { Btn } from '../Components/Btn'; import { Link, Spacer, Truncate } from '../Components/CoreUI'; import { PluginModal } from '../Components/PluginModal'; import { RemoteModal } from '../Components/RemoteModal'; import { Sub } from '../Components/Text'; import dfstyles from '../Styles/dfstyles'; import { useEmitterValue } from '../Utils/EmitterHooks'; import { getBooleanSetting, setSetting, useBooleanSetting } from '../Utils/SettingsHooks'; import { ModalPane } from '../Views/ModalPane'; import { PluginEditorPane } from './PluginEditorPane'; function HelpContent() { return (

Plugins are bits of code that can be written by anyone, and allows the writer to program the game. Plugins range from cosmetic (try the rage cage plugin) to functional (imagine a plugin that fights your wars for you).

Dark Forest maintains a repository to which community members can submit their own plugins. You can find it here.

Try editing one of the default plugins to see how it works!

); } const Actions = styled.div` float: right; .blue { --df-button-hover-background: ${dfstyles.colors.dfblue}; --df-button-hover-border: 1px solid ${dfstyles.colors.dfblue}; } .red { --df-button-hover-background: ${dfstyles.colors.dfred}; --df-button-hover-border: 1px solid ${dfstyles.colors.dfred}; } .green { --df-button-hover-background: ${dfstyles.colors.dfgreen}; --df-button-hover-border: 1px solid ${dfstyles.colors.dfgreen}; } `; /** * This modal presents an overview of all of the player's plugins. Has a button to add a new plugin, * and lists out all the existing plugins, allowing the user to view their titles, as well as either * edit, delete, or open their modal. * * You can think of this as the plugin process list, the Activity Monitor of Dark forest. */ export function PluginLibraryPane({ gameUIManager, visible, onClose, modalsContainer, }: { gameUIManager: GameUIManager; visible: boolean; onClose: () => void; modalsContainer: Element; }) { const pluginManager = gameUIManager.getPluginManager(); const modalManager = gameUIManager.getModalManager(); const plugins = useEmitterValue(pluginManager.plugins$, pluginManager.getLibrary()); const contractAddress = gameUIManager.getContractAddress(); const account = gameUIManager.getAccount(); const config = { contractAddress, account }; const isAdmin = gameUIManager.isAdmin(); const [editorIsOpen, setEditorIsOpen] = useState(false); const [warningIsOpen, setWarningIsOpen] = useState(false); const [clicksUntilHasPlugins, setClicksUntilHasPlugins] = useState(8); const [forceReloadEmbeddedPlugins, _s] = useBooleanSetting( gameUIManager, Setting.ForceReloadEmbeddedPlugins ); /** * the id of the plugin that the user is currently editing. */ const [currentlyEditingPluginId, setEditingPluginId] = useState(); /** * to get a unique editor for every time we open the editor. this means that every * time you open the editor, you get a fresh copy of your plugin, or a blank state. * if we did not do this, then the previous unsaved edits would persist in the editor * ui. */ const [editorNonce, setEditorNonce] = useState(0); /** * Opens an editor that would overwrite an existing plugin if one * exists for the given plugin id. If one doesn't exist, opens * an editor that will save a new plugin. Returns a function that * closes the editor. */ function openEditorForPlugin(pluginId?: PluginId) { if (!account || !getBooleanSetting(config, Setting.HasAcceptedPluginRisk)) { setWarningIsOpen(true); return; } setWarningIsOpen(false); setEditorIsOpen(true); setEditorNonce(editorNonce + 1); if (currentlyEditingPluginId !== pluginId) { setEditingPluginId(pluginId); } } function runPluginClicked(pluginId: PluginId) { modalManager.setModalState(pluginId, 'open'); } /** * Overwrites the plugin with the given plugin id, killing its process * if it has a process. If `pluginId` is undefined, saves a new plugin. */ const saveAndReloadPlugin = (newName: string, newCode: string, pluginId?: PluginId): void => { if (pluginId && newCode) { pluginManager?.overwritePlugin(newName || 'no name', newCode, pluginId); } else { // Auto generate a PluginId const pluginId = uuidv4() as PluginId; pluginManager?.addPluginToLibrary(pluginId, newName || 'no name', newCode || ''); } }; const onAcceptWarningClick = () => { if (clicksUntilHasPlugins === 1) { account && setSetting(config, Setting.HasAcceptedPluginRisk, true + ''); setWarningIsOpen(false); } setClicksUntilHasPlugins(clicksUntilHasPlugins - 1); }; /** * When we first load this component, make sure that we've loaded all * the plugins from disk. */ useEffect(() => { pluginManager.load(isAdmin, forceReloadEmbeddedPlugins); }, [pluginManager, isAdmin, forceReloadEmbeddedPlugins]); function addPluginClicked(): void { openEditorForPlugin(undefined); } function deletePluginClicked(pluginId: PluginId) { if (confirm('are you sure you want to delete this plugin?')) { pluginManager.deletePlugin(pluginId); modalManager.clearModalPosition(pluginId); setEditorIsOpen(false); } } function onPluginReorder(newOrder: SerializedPlugin[]) { pluginManager?.reorderPlugins(newOrder.map((p) => p.id)); } /** * The Dark Forest process list. */ function renderPluginsList() { if (plugins.length === 0) { return 'you have no plugins!'; } return ( {plugins.map((plugin) => (
{plugin.name} openEditorForPlugin(plugin.id)}> edit deletePluginClicked(plugin.id)}> del runPluginClicked(plugin.id)}> run
))}
); } function onPluginClosed(pluginId: PluginId) { pluginManager.destroy(pluginId); modalManager.setModalState(pluginId, 'closed'); } function onPluginRendered(pluginId: PluginId, el: HTMLDivElement) { // This is `async` but we don't care about the result pluginManager.render(pluginId, el); } const pluginModals = plugins.map((plugin) => { return ( onPluginClosed(plugin.id)} onRender={(el) => onPluginRendered(plugin.id, el)} /> ); }); return ( <> setWarningIsOpen(false)} width={RECOMMENDED_MODAL_WIDTH} >

Dark Forest supports plugins, which allow you to write JavaScript code that can interact with the game. Plugins are powerful and can enhance your gameplay experience, but they can also be dangerous!


Be careful using plugins that were authored by somebody other than yourself! Plugins can impersonate your account, and steal all your money. A malicious plugin could transfer all your planets and artifacts to somebody else!


Click {clicksUntilHasPlugins} times for Plugins
setEditorIsOpen(false)} > {pluginModals} {renderPluginsList()} Add Plugin ); } ================================================ FILE: src/Frontend/Panes/PrivatePane.tsx ================================================ import { ModalName } from '@darkforest_eth/types'; import React, { useEffect, useState } from 'react'; import styled from 'styled-components'; import { Sub } from '../Components/Text'; import { TextPreview } from '../Components/TextPreview'; import { useUIManager } from '../Utils/AppHooks'; import { ModalPane } from '../Views/ModalPane'; const StyledPrivatePane = styled.div` width: 36em; height: 10em; & > div { display: flex; flex-direction: row; justify-content: space-between; } `; export function PrivatePane({ visible, onClose }: { visible: boolean; onClose: () => void }) { const uiManager = useUIManager(); const [sKey, setSKey] = useState(undefined); const [home, setHome] = useState(undefined); useEffect(() => { if (!uiManager) return; setSKey(uiManager.getPrivateKey()); const coords = uiManager.getHomeCoords(); setHome(coords ? `(${coords.x}, ${coords.y})` : ''); }, [uiManager]); return (

secret key


Home Coords

{home}

); } ================================================ FILE: src/Frontend/Panes/SettingsPane.tsx ================================================ import { EthConnection } from '@darkforest_eth/network'; import { AutoGasSetting, Chunk, ModalName, Setting } from '@darkforest_eth/types'; import React, { useEffect, useState } from 'react'; import styled from 'styled-components'; import TutorialManager from '../../Backend/GameLogic/TutorialManager'; import { Btn } from '../Components/Btn'; import { Section, SectionHeader, Spacer } from '../Components/CoreUI'; import { DarkForestTextInput, TextInput } from '../Components/Input'; import { Slider } from '../Components/Slider'; import { Green, Red } from '../Components/Text'; import Viewport, { getDefaultScroll } from '../Game/Viewport'; import { useAccount, useUIManager } from '../Utils/AppHooks'; import { useEmitterValue } from '../Utils/EmitterHooks'; import { BooleanSetting, ColorSetting, MultiSelectSetting, NumberSetting, } from '../Utils/SettingsHooks'; import { ModalPane } from '../Views/ModalPane'; const SCROLL_MIN = 0.0001 * 10000; const SCROLL_MAX = 0.01 * 10000; const DEFAULT_SCROLL = Math.round(10000 * (getDefaultScroll() - 1)); const SettingsContent = styled.div` width: 500px; height: 500px; overflow-y: scroll; display: flex; flex-direction: column; text-align: justify; `; const Row = styled.div` display: flex; flex-direction: row; justify-content: space-between; align-items: center; & > span:first-child { flex-grow: 1; } `; export function SettingsPane({ ethConnection, visible, onClose, onOpenPrivate, }: { ethConnection: EthConnection; visible: boolean; onClose: () => void; onOpenPrivate: () => void; }) { const uiManager = useUIManager(); const account = useAccount(uiManager); const isDevelopment = process.env.NODE_ENV !== 'production'; const gasPrices = useEmitterValue(ethConnection.gasPrices$, ethConnection.getAutoGasPrices()); const [rpcUrl, setRpcURL] = useState(ethConnection.getRpcEndpoint()); const onChangeRpc = () => { ethConnection .setRpcUrl(rpcUrl) .then(() => { localStorage.setItem('XDAI_RPC_ENDPOINT_v5', rpcUrl); }) .catch(() => { setRpcURL(ethConnection.getRpcEndpoint()); }); }; const [balance, setBalance] = useState(0); useEffect(() => { if (!uiManager) return; const updateBalance = () => { setBalance(uiManager.getMyBalance()); }; updateBalance(); const intervalId = setInterval(updateBalance, 1000); return () => { clearInterval(intervalId); }; }, [uiManager]); const [failure, setFailure] = useState(''); const [success, setSuccess] = useState(''); const [importMapByTextBoxValue, setImportMapByTextBoxValue] = useState(''); useEffect(() => { if (failure) { setSuccess(''); } }, [failure]); useEffect(() => { if (success) { setFailure(''); } }, [success]); const onExportMap = async () => { if (uiManager) { const chunks = uiManager.getExploredChunks(); const chunksAsArray = Array.from(chunks); try { const map = JSON.stringify(chunksAsArray); await window.navigator.clipboard.writeText(map); setSuccess('Copied map!'); } catch (err) { console.error(err); setFailure('Failed to export'); } } else { setFailure('Unable to export map right now.'); } }; const onImportMapFromTextBox = async () => { try { const chunks = JSON.parse(importMapByTextBoxValue); await uiManager.bulkAddNewChunks(chunks as Chunk[]); setImportMapByTextBoxValue(''); } catch (e) { setFailure('Invalid map data. Check the data in your clipboard.'); } }; const onImportMap = async () => { if (uiManager) { let input; try { input = await window.navigator.clipboard.readText(); } catch (err) { console.error(err); setFailure('Unable to import map. Did you allow clipboard access?'); return; } let chunks; try { chunks = JSON.parse(input); } catch (err) { console.error(err); setFailure('Invalid map data. Check the data in your clipboard.'); return; } await uiManager.bulkAddNewChunks(chunks as Chunk[]); setSuccess('Successfully imported a map!'); } else { setFailure('Unable to import map right now.'); } }; const [clicks, setClicks] = useState(8); const doPrivateClick = () => { setClicks((x) => x - 1); if (clicks === 1) { onOpenPrivate(); setClicks(5); } }; const [scrollSpeed, setScrollSpeed] = useState(DEFAULT_SCROLL); const onScrollChange = (e: Event & React.ChangeEvent) => { const value = parseFloat(e.target.value); if (!isNaN(value)) setScrollSpeed(value); }; useEffect(() => { const scroll = localStorage.getItem('scrollSpeed'); if (scroll) { setScrollSpeed(10000 * (parseFloat(scroll) - 1)); } }, [setScrollSpeed]); useEffect(() => { if (!Viewport.instance) return; Viewport.instance.setMouseSensitivty(scrollSpeed / 10000); }, [scrollSpeed]); return ( {isDevelopment && (
Development
)}
Burner Wallet Info Public Key {account} Balance {balance}
Gas Price Your gas price setting determines the price you pay for each transaction. A higher gas price means your transactions will be prioritized by the blockchain, making them confirm faster. We recommend using the auto average setting. All auto settings prices are pulled from an oracle and are capped at 15 gwei.
Burner Wallet Info (Private) Your secret key, together with your home planet's coordinates, grant you access to your Dark Forest account on different browsers. You should save this info somewhere on your computer. WARNING: Never ever send this to anyone! Click {clicks} times to view info
Auto Confirm Transactions Whether or not to auto-confirm all transactions, except purchases. This will allow you to make moves, spend silver on upgrades, etc. without requiring you to confirm each transaction. However, the client WILL ask for confirmation before sending transactions that spend wallet funds.
Import and Export Map Data WARNING: Maps from others could be altered and are not guaranteed to be correct! ) => setImportMapByTextBoxValue(e.target.value) } /> Import Map From Above Copy Map to Clipboard Import Map from Clipboard {success} {failure}
Change RPC Endpoint Current RPC Endpoint: {rpcUrl} ) => setRpcURL(e.target.value) } /> Change RPC URL
Metrics Opt Out We collect a minimal set of data and statistics such as SNARK proving times, average transaction times across browsers, and xDAI transaction errors, to help us optimize performance and fix bugs. This does not include personal data like email or IP address.
Performance High performance mode turns off background rendering, and reduces the detail at which smaller planets are rendered.
Notifications Auto clear transaction confirmation notifications after this many seconds. Set to a negative number to not auto-clear. Auto clear transaction rejection notifications after this many seconds. Set to a negative number to not auto-clear.
Scroll speed
Reset Tutorial TutorialManager.getInstance(uiManager).reset()}> Reset Tutorial
Disable Default Shortcuts If you'd like to use custom shortcuts via a plugin, you can disable the default shortcuts here.
Enable Experimental Features Features that aren't quite ready for production but we think are cool.
Renderer Settings Some options for the default renderer which is included with the game.
); } ================================================ FILE: src/Frontend/Panes/Tooltip.tsx ================================================ import { RECOMMENDED_MODAL_WIDTH } from '@darkforest_eth/constants'; import { TooltipName } from '@darkforest_eth/types'; import React, { useEffect, useLayoutEffect, useRef, useState } from 'react'; import ReactDOM from 'react-dom'; import styled from 'styled-components'; import dfstyles from '../Styles/dfstyles'; import { useOverlayContainer } from '../Utils/AppHooks'; import { DFZIndex } from '../Utils/constants'; import { TooltipContent } from './TooltipPanes'; /** * Each {@link TooltipName} has a corresponding tooltip element. */ export interface TooltipTriggerProps { /** * The name of the tooltip element to display. You can see all the concrete tooltip contents in * the file called {@link TooltipPanes}. Set to `undefined` to not render the tooltip. */ name: TooltipName | undefined; /** * A {@link TooltipTrigger} wraps this child, and causes a tooltip to appear when the user hovers * over it. */ children: React.ReactNode; /** * You can append some dynamic content to the given tooltip by setting this field to a React node. */ extraContent?: React.ReactNode; /** * You can optionally style the tooltip trigger element, not the tooltip itself. */ style?: React.CSSProperties; } export interface TooltipProps extends TooltipTriggerProps { top: number; left: number; } /** * When the player hovers over this element, triggers the tooltip with the given name to be * displayed on top of everything. */ export function TooltipTrigger(props: TooltipTriggerProps) { const overlayContainer = useOverlayContainer(); const [hovering, setHovering] = useState(false); const [mouseCoords, setMouseCoords] = useState({ x: 0, y: 0 }); useEffect(() => { const doMouseMove = (e: MouseEvent) => { setMouseCoords({ x: e.clientX, y: e.clientY }); }; window.addEventListener('mousemove', doMouseMove); return () => { window.removeEventListener('mousemove', doMouseMove); }; }); return ( <> setHovering(true)} onMouseLeave={() => setHovering(false)} > {props.children} {overlayContainer && hovering && ReactDOM.createPortal( , overlayContainer )} ); } /** * At any given moment, there can only be one tooltip visible in the game. This is true because a * player only has one mouse cursor on the screen, and therefore can only be hovering over a single * {@link TooltipTrigger} element at any given time. This component is responsible for keeping track * of which tooltip has been hovered over, and rendering the corresponding content. */ export function Tooltip(props: TooltipProps) { const elRef = useRef(document.createElement('div')); const [height, setHeight] = useState(20); const [width, setWidth] = useState(20); const [leftOffset, setLeftOffset] = useState(10); const [topOffset, setTopOffset] = useState(10); // sync size to content size useLayoutEffect(() => { setHeight(elRef.current.offsetHeight); setWidth(elRef.current.offsetWidth); }, [elRef.current.offsetHeight, elRef]); // point it in the right direction based on quadrant useLayoutEffect(() => { if (props.left < window.innerWidth / 2) { setLeftOffset(10); } else { setLeftOffset(-10 - width); } if (props.top < window.innerHeight / 2) { setTopOffset(10); } else { setTopOffset(-10 - height); } }, [width, height, props.left, props.top]); if (props.name !== undefined) { return ( e.preventDefault()} onMouseLeave={(e) => e.preventDefault()} style={{ top: `${props.top + topOffset}px`, left: `${props.left + leftOffset}px`, }} > {props.extraContent} ); } return null; } const StyledTooltipTrigger = styled.span` border-radius: 2px; display: ${(props) => props?.style?.display || 'inline'}; `; const StyledTooltip = styled.div` max-width: ${RECOMMENDED_MODAL_WIDTH}; position: absolute; border: 1px solid ${dfstyles.colors.border}; background: ${dfstyles.colors.background}; padding: 0.5em 1em; border-radius: 3px; line-height: 1.5em; z-index: ${DFZIndex.Tooltip}; `; ================================================ FILE: src/Frontend/Panes/TooltipPanes.tsx ================================================ import { PlanetType, TooltipName } from '@darkforest_eth/types'; import React from 'react'; import { getPlanetRank, isFullRank } from '../../Backend/Utils/Utils'; import { ScoreLabel, SilverLabel } from '../Components/Labels/KeywordLabels'; import { Green, Red, Text, White } from '../Components/Text'; import { useAccount, useSelectedPlanet, useUIManager } from '../Utils/AppHooks'; export function NetworkHealthPane() { return ( <> xDAI Tx Speed: For each auto gas setting (which you can choose in the{' '} Settings Pane), the average amount of time it takes a transaction with that setting to confirm. The Dark Forest client uploads diagnostic info (you can turn this off via settings), which is aggregated into this network health indicator. I hope you find it helpful in cases the network is being slow. ); } export function WithdrawSilverButton() { return ( <> This is a Spacetime Rip where you can withdraw for ! ); } export function DefenseMultiplierPane() { return <>Defense multiplier; } export function EnergyCapMultiplierPane() { return <>EnergyCap multiplier; } export function EnergyGrowthMultiplierPane() { return <>EnergyGrowth multiplier; } export function RangeMultiplierPane() { return <>Range multiplier; } export function SpeedMultiplierPane() { return <>Speed multiplier; } export function DepositArtifactPane() { return <>Deposit this artifact; } export function DeactivateArtifactPane() { return <>Deactivate this artifact; } export function WithdrawArtifactPane() { return <>Withdraw this artifact; } export function ActivateArtifactPane() { return <>Activate this artifact; } export function TimeUntilActivationPossiblePane() { return <>You must wait this amount of time before you can activate this artifact; } export function TwitterHandleTooltipPane() { return ( <> You may connect your account to Twitter
to identify yourself on the Leaderboard. ); } export function RankTooltipPane() { return ( <> Your current rank, based on . ); } export function ScoreTooltipPane() { return ( <> You earn by finding artifacts and withdrawing silver. Check out the{' '} Help Pane for more info on scoring. ); } export function MiningPauseTooltipPane() { return ( <> Start / Stop your explorer. Your explorer looks for planets in chunks of{' '} 16 x 16. ); } export function MiningTargetTooltipPane() { return ( <> Change the location of your explorer. Click anywhere on the{' '} Game Screen, and your miner will start hashing around that chunk. ); } export function CurrentMiningTooltipPane() { return ( <> The current coordinates of your explorer. ); } export function BonusTooltipPane() { return ( <> This stat has been randomly doubled! ); } export function SilverTooltipPane() { return ( <> Silver: the universe's monetary resource. It allows you to buy upgrades. Only Asteroid Fields produce silver or so we've been told... ); } export function EnergyTooltipPane() { return ( <> Energy: allows you to make moves. Energy grows following an{' '} s-curve, and grows fastest at 50% capacity. ); } export function PlanetRankTooltipPane() { const uiManager = useUIManager(); const selected = useSelectedPlanet(uiManager); const rank = getPlanetRank(selected.value); return ( <> This planet is {isFullRank(selected.value) ? 'fully upgraded' : 'rank ' + rank} ! ); } export function MaxLevelTooltipPane() { return ( <> This planet is Level 9, making it one of the
most powerful planets in the galaxy! ); } export function SilverProdTooltipPane() { return ( <> This planet produces Silver! Use it to buy upgrades! ); } export function SelectedSilverTooltipPane() { const uiManager = useUIManager(); const selected = useSelectedPlanet(uiManager); return ( <> {selected.value ? ( <> <> Silver: {selected.value.silver} <> Cap: {selected.value.silverCap} {selected.value.planetType === PlanetType.SILVER_MINE ? ( <> Growth: {selected.value.silverGrowth * 60} ) : ( <> This planet does not produce silver. )} ) : ( <>Select a planet to view more about its stats. )} ); } export function RangeTooltipPane() { return ( <> Range: how far you can send your forces. Forces decay the farther out you send them.
Higher range means that you can send forces the same distance with less decay. ); } export function MinEnergyTooltipPane() { return ( <> The minimum energy you need to send a move from this planet.
Moves incur a base cost of 5% of the planet's Energy Cap. ); } export function Time50TooltipPane() { return ( <> Time to 50% of full energy. ); } export function Time90TooltipPane() { return ( <> Time to 90% of full energy. Since energy grows on an s-curve, energy growth slows drastically by this point. ); } export function EnergyGrowthTooltipPane() { return ( <> Energy Growth: the maximum growth rate of this planet's energy representing the rate at the middle of the s-curve. ); } export function SilverGrowthTooltipPane() { return ( <> Silver Growth: the per-minute linear growth rate of this planet's silver. ); } export function SilverCapTooltipPane() { return ( <> Silver Cap: the maximum silver that this planet can hold. ); } export function PiratesTooltipPane() { return ( <> This planet has space pirates! Move energy to unoccupied planets to conquer them! ); } export function UpgradesTooltipPane() { return ( <> Planet Rank: the number of times you've upgraded your planet. ); } export function ModalHelpTooltipPane() { return <>View patch notes and instructions; } export function ModalPlanetDetailsTooltipPane() { return <>View detailed information about the selected planet; } export function ModalLeaderboardTooltipPane() { return <>View the top players, and their top planets; } export function ModalPlanetDexTooltipPane() { return <>View a list of your planets; } export function ModalUpgradeDetailsTooltipPane() { return <>Upgrade the selected planet; } export function ModalTwitterVerificationTooltipPane() { return <>Connect your address to Twitter; } export function ModalBroadcastTooltipPane() { return <>Broadcast the selected planet's coordinates to the world; } export function BonusEnergyCapTooltipPane() { return ( <> This planet's Energy Cap has been randomly doubled! ); } export function BonusEnergyGroTooltipPane() { return ( <> This planet's Energy Growth has been randomly doubled! ); } export function BonusRangeTooltipPane() { return ( <> This planet's Range has been randomly doubled! ); } export function BonusSpeedTooltipPane() { return ( <> This planet's Speed has been randomly doubled! ); } export function BonusDefenseTooltipPane() { return ( <> This planet's Defense has been randomly doubled! ); } export function BonusSpaceJunkTooltipPane() { return ( <> This planet's Space Junk has been randomly halved! ); } export function ClowntownTooltipPane() { const uiManager = useUIManager(); const selected = useSelectedPlanet(uiManager); const account = useAccount(uiManager); return ( <> {selected.value?.owner === account ? `You are the proud mayor of Clown Town!` : `It's a town of clowns...`} ); } function DefenseTooltipPane() { return ( <> Defense: Planets with higher defense will negate incoming damage. Planets with lower than 100 defense are vulnerable and will take more damage! ); } function SpaceJunkTooltipPane() { return ( <> Space Junk: Planets are all filled with junk! Sending energy to a planet with junk will remove the junk from that planet and add it to your total junk. Once you reach your junk limit, you will not be able to capture planets that have junk. Abandoning planets will reduce your space junk and place it back on the planet. ); } function AbandonTooltipPane() { const uiManager = useUIManager(); const abandonSpeedBoost = uiManager.getAbandonSpeedChangePercent() / 100; const abandonRangeBoost = uiManager.getAbandonRangeChangePercent() / 100; return ( <> Abandon your planet: Give up ownership of this planet to dump some of your space junk here. This triggers a special movement that sends full Energy/Silver and gives a Range boost of {abandonRangeBoost}x and a{' '} Speed boost of {abandonSpeedBoost}x.
You cannot abandon your home planet, or a planet that has incoming voyages. ); } function SpeedTooltipPane() { return ( <> Speed: The rate at which energy travels across the universe, the faster the better! ); } function RetryTransactionPane() { return <>Retry transaction.; } function CancelTransactionPane() { return <>Cancel transaction.; } function PrioritizeTransactionPane() { return <>Prioritize transaction.; } function ArtifactBuffPane() { return <>A powerful artifact on this planet is influencing this stat!; } function PluginsTooltipPane() { return <>Manage plugins, which allow you to add functionality to the client.; } function SettingsPane() { return <>Manage settings - export SKEY, manage maps, and more.; } function YourArtifacts() { return <>View your artifacts.; } function InvadablePane() { return <>This planet is in a scoring zone and can be invaded; } function CapturablePane() { return <>This planet has been invaded, which means you can capture it for score.; } const ModalWithdrawSilverTooltipPane = () => <>Withdraw silver to earn score.; const Hats = () => <>Buy hats for the selected planet.; const FindArtifact = () => ( <> This planet has a powerful artifact hidden somewhere! Maybe you could find it... ); const ArtifactStored = () => <>This planet has a powerful artifact on it!; const HashesPerSec = () => <>hashes / sec; export function TooltipContent({ name }: { name: TooltipName | undefined }) { if (name === TooltipName.SilverGrowth) return ; if (name === TooltipName.SilverCap) return ; if (name === TooltipName.Silver) return ; if (name === TooltipName.Energy) return ; if (name === TooltipName.EnergyGrowth) return ; if (name === TooltipName.Range) return ; if (name === TooltipName.TwitterHandle) return ; if (name === TooltipName.Bonus) return ; if (name === TooltipName.MinEnergy) return ; if (name === TooltipName.Time50) return ; if (name === TooltipName.Time90) return ; if (name === TooltipName.Pirates) return ; if (name === TooltipName.Upgrades) return ; if (name === TooltipName.PlanetRank) return ; if (name === TooltipName.MaxLevel) return ; if (name === TooltipName.SelectedSilver) return ; if (name === TooltipName.Rank) return ; if (name === TooltipName.Score) return ; if (name === TooltipName.MiningPause) return ; if (name === TooltipName.MiningTarget) return ; if (name === TooltipName.CurrentMining) return ; if (name === TooltipName.SilverProd) return ; if (name === TooltipName.BonusEnergyCap) return ; if (name === TooltipName.BonusEnergyGro) return ; if (name === TooltipName.BonusRange) return ; if (name === TooltipName.BonusSpeed) return ; if (name === TooltipName.BonusDefense) return ; if (name === TooltipName.BonusSpaceJunk) return ; if (name === TooltipName.Clowntown) return ; if (name === TooltipName.ModalHelp) return ; if (name === TooltipName.ModalPlanetDetails) return ; if (name === TooltipName.ModalLeaderboard) return ; if (name === TooltipName.ModalPlanetDex) return ; if (name === TooltipName.ModalUpgradeDetails) return ; if (name === TooltipName.ModalTwitterVerification) return ; if (name === TooltipName.ModalTwitterBroadcast) return ; if (name === TooltipName.Defense) return ; if (name === TooltipName.SpaceJunk) return ; if (name === TooltipName.Abandon) return ; if (name === TooltipName.Speed) return ; if (name === TooltipName.ArtifactBuff) return ; if (name === TooltipName.ModalPlugins) return ; if (name === TooltipName.ModalSettings) return ; if (name === TooltipName.ModalYourArtifacts) return ; if (name === TooltipName.ModalHats) return ; if (name === TooltipName.FindArtifact) return ; if (name === TooltipName.ArtifactStored) return ; if (name === TooltipName.HashesPerSec) return ; if (name === TooltipName.ModalWithdrawSilver) return ; if (name === TooltipName.TimeUntilActivationPossible) return ; if (name === TooltipName.DepositArtifact) return ; if (name === TooltipName.DeactivateArtifact) return ; if (name === TooltipName.WithdrawArtifact) return ; if (name === TooltipName.ActivateArtifact) return ; if (name === TooltipName.DefenseMultiplier) return ; if (name === TooltipName.EnergyCapMultiplier) return ; if (name === TooltipName.EnergyGrowthMultiplier) return ; if (name === TooltipName.RangeMultiplier) return ; if (name === TooltipName.SpeedMultiplier) return ; if (name === TooltipName.NetworkHealth) return ; if (name === TooltipName.WithdrawSilverButton) return ; if (name === TooltipName.RetryTransaction) return ; if (name === TooltipName.CancelTransaction) return ; if (name === TooltipName.PrioritizeTransaction) return ; if (name === TooltipName.Invadable) return ; if (name === TooltipName.Capturable) return ; return <>; } ================================================ FILE: src/Frontend/Panes/TransactionLogPane.tsx ================================================ import { RECOMMENDED_MODAL_WIDTH } from '@darkforest_eth/constants'; import { getPlanetName } from '@darkforest_eth/procedural'; import { isUnconfirmedActivateArtifactTx, isUnconfirmedBuyHatTx, isUnconfirmedCapturePlanetTx, isUnconfirmedDeactivateArtifactTx, isUnconfirmedDepositArtifactTx, isUnconfirmedFindArtifactTx, isUnconfirmedInitTx, isUnconfirmedInvadePlanetTx, isUnconfirmedMoveTx, isUnconfirmedProspectPlanetTx, isUnconfirmedRevealTx, isUnconfirmedTransferTx, isUnconfirmedUpgradeTx, isUnconfirmedWithdrawArtifactTx, isUnconfirmedWithdrawSilverTx, } from '@darkforest_eth/serde'; import { ModalName, Planet, TooltipName, Transaction } from '@darkforest_eth/types'; import { IconType } from '@darkforest_eth/ui'; import { isEmpty, reverse, startCase, values } from 'lodash'; import React, { useCallback } from 'react'; import Loader from 'react-loader-spinner'; import 'react-loader-spinner/dist/loader/css/react-spinner-loader.css'; import TimeAgo from 'react-timeago'; import styled from 'styled-components'; import GameUIManager from '../../Backend/GameLogic/GameUIManager'; import { Wrapper } from '../../Backend/Utils/Wrapper'; import { Btn } from '../Components/Btn'; import { CenterBackgroundSubtext, Spacer } from '../Components/CoreUI'; import { Icon } from '../Components/Icons'; import { Sub, TxLink } from '../Components/Text'; import dfstyles from '../Styles/dfstyles'; import { TransactionRecord, useTransactionLog, useUIManager } from '../Utils/AppHooks'; import { ModalPane } from '../Views/ModalPane'; import { PlanetLink } from '../Views/PlanetLink'; import { SortableTable } from '../Views/SortableTable'; import { PlanetThumb } from './PlanetDexPane'; import { TooltipTrigger } from './Tooltip'; const PlanetName = styled.span` display: block; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 120px; `; const TableContainer = styled.div` min-width: 500px; overflow-y: scroll; `; function TransactionState({ tx }: { tx: Transaction }) { let element; if (tx.state === 'Init') { element = ( ); } else if (tx.state === 'Prioritized') { element = ( ); } else if (['Submit', 'Processing'].includes(tx.state)) { element = ( ); } else if (tx.state === 'Confirm') { element = ( {' '} ); } else if (tx.state === 'Cancel') { element = ( ); } else { element = ( ); } return (
{element}
); } const ActionButton = styled(Btn)` height: 24px; width: 36px; `; const ActionContainer = styled.div` min-width: 96px; `; const TransactionActions = ({ tx, cancelTransaction, retryTransaction, prioritizeTransaction, }: { tx: Transaction; cancelTransaction: (tx: Transaction) => void; retryTransaction: (tx: Transaction) => void; prioritizeTransaction: (tx: Transaction) => void; }) => { let actions; if (tx.state === 'Fail') { actions = ( retryTransaction(tx)}> ); } else if (tx.state === 'Init') { actions = (
prioritizeTransaction(tx)}> cancelTransaction(tx)}>
); } else if (tx.state === 'Prioritized') { actions = ( cancelTransaction(tx)}> ); } return {actions || '-'}; }; const humanizeTransactionType = (tx: Transaction) => startCase(tx.intent.methodName); /** * Grab the planet associated with the specified transction. Unfortunately, transaction intent * is not standardized right now, so we need to know all of the different ways the planet location * id is stored. */ const getPlanetFromTransaction = ( uiManager: GameUIManager, tx: Transaction ): Planet | undefined => { const gameManager = uiManager.getGameManager(); if (isUnconfirmedMoveTx(tx)) return gameManager.getPlanetWithId(tx.intent.from); if (isUnconfirmedUpgradeTx(tx)) return gameManager.getPlanetWithId(tx.intent.locationId); if (isUnconfirmedActivateArtifactTx(tx)) return gameManager.getPlanetWithId(tx.intent.locationId); if (isUnconfirmedRevealTx(tx)) return gameManager.getPlanetWithId(tx.intent.locationId); if (isUnconfirmedInitTx(tx)) return gameManager.getPlanetWithId(tx.intent.locationId); if (isUnconfirmedBuyHatTx(tx)) return gameManager.getPlanetWithId(tx.intent.locationId); if (isUnconfirmedTransferTx(tx)) return gameManager.getPlanetWithId(tx.intent.planetId); if (isUnconfirmedFindArtifactTx(tx)) return gameManager.getPlanetWithId(tx.intent.planetId); if (isUnconfirmedDepositArtifactTx(tx)) return gameManager.getPlanetWithId(tx.intent.locationId); if (isUnconfirmedWithdrawArtifactTx(tx)) return gameManager.getPlanetWithId(tx.intent.locationId); if (isUnconfirmedProspectPlanetTx(tx)) return gameManager.getPlanetWithId(tx.intent.planetId); if (isUnconfirmedDeactivateArtifactTx(tx)) return gameManager.getPlanetWithId(tx.intent.locationId); if (isUnconfirmedWithdrawSilverTx(tx)) return gameManager.getPlanetWithId(tx.intent.locationId); if (isUnconfirmedInvadePlanetTx(tx)) return gameManager.getPlanetWithId(tx.intent.locationId); if (isUnconfirmedCapturePlanetTx(tx)) return gameManager.getPlanetWithId(tx.intent.locationId); }; function QueuedTransactionsTable({ transactions }: { transactions: Wrapper }) { const uiManager = useUIManager(); const visibleTransactions = reverse(values(transactions.value)); const headers = ['Type', 'Hash', 'State', 'Planet', 'Updated', 'Actions']; const alignments: Array<'r' | 'c' | 'l'> = ['c', 'c', 'c', 'c', 'c', 'c']; const cancelTransaction = useCallback( (tx: Transaction) => { uiManager.getGameManager().getContractAPI().cancelTransaction(tx); }, [uiManager] ); const retryTransaction = useCallback( (tx: Transaction) => { uiManager.getGameManager().getContractAPI().submitTransaction(tx.intent); }, [uiManager] ); const prioritizeTransaction = useCallback( (tx: Transaction) => { uiManager.getGameManager().getContractAPI().prioritizeTransaction(tx); }, [uiManager] ); const queuedTransctions = useCallback(() => { return values(transactions.value).filter((tx) => ['Init', 'Prioritized'].includes(tx.state)); }, [transactions]); const cancelAllQueuedTransactions = useCallback(() => { queuedTransctions().forEach((queuedTx) => { try { cancelTransaction(queuedTx); } catch {} }); }, [cancelTransaction, queuedTransctions]); const columns = [ (tx: Transaction) => ( {humanizeTransactionType(tx)} ), (tx: Transaction) => (
), (tx: Transaction) => , (tx: Transaction) => { const planet = getPlanetFromTransaction(uiManager, tx); if (!planet) return <>; return (
{getPlanetName(planet)}
); }, (tx: Transaction) => ( { let newUnit = unit as string; if (unit === 'second' && value === 0) return 'just now'; if (unit === 'second') newUnit = 's'; if (unit === 'minute') newUnit = 'm'; if (unit === 'hour') newUnit = 'h'; if (unit === 'day') newUnit = 'd'; return `${value}${newUnit} ${suffix}`; }} /> ), (tx: Transaction) => ( ), ]; const sortingFunctions = [ (a: Transaction, b: Transaction): number => a.intent.methodName.localeCompare(b.intent.methodName), (_a: Transaction, _b: Transaction): number => 0, (a: Transaction, b: Transaction): number => a.state.localeCompare(b.state), (a: Transaction, b: Transaction): number => { const planetA = getPlanetFromTransaction(uiManager, a); if (!planetA) return -1; const planetB = getPlanetFromTransaction(uiManager, b); if (!planetB) return 1; return getPlanetName(planetB).localeCompare(getPlanetName(planetA)); }, (a: Transaction, b: Transaction): number => b.lastUpdatedAt - a.lastUpdatedAt, ]; return (
{queuedTransctions().length !== 0 && ( )} {queuedTransctions().length} queued transactions {queuedTransctions().length !== 0 && ( cancel all )}
); } export function TransactionLogPane({ visible, onClose, }: { visible: boolean; onClose: () => void; }) { const transactions = useTransactionLog(); return ( {isEmpty(transactions.value) ? ( No transactions to be shown ) : ( )} ); } ================================================ FILE: src/Frontend/Panes/TutorialPane.tsx ================================================ import { Setting } from '@darkforest_eth/types'; import React, { useEffect, useState } from 'react'; import styled from 'styled-components'; import TutorialManager, { TutorialManagerEvent, TutorialState, } from '../../Backend/GameLogic/TutorialManager'; import { Hook } from '../../_types/global/GlobalTypes'; import { Btn } from '../Components/Btn'; import { Underline } from '../Components/CoreUI'; import { Icon, IconType } from '../Components/Icons'; import { White } from '../Components/Text'; import dfstyles from '../Styles/dfstyles'; import { useUIManager } from '../Utils/AppHooks'; import { useBooleanSetting } from '../Utils/SettingsHooks'; function TutorialPaneContent({ tutorialState }: { tutorialState: TutorialState }) { const uiManager = useUIManager(); const tutorialManager = TutorialManager.getInstance(uiManager); if (tutorialState === TutorialState.None) { return (
Welcome to the universe of DARK FOREST. Would you like to play the tutorial?
tutorialManager.acceptInput(TutorialState.None)}> Yes tutorialManager.complete()}> No
); } else if (tutorialState === TutorialState.HomePlanet) { return (
Welcome to the universe. You've initialized with 50 energy on your home planet, in a NEBULA.

Click your home planet to learn more.
); } else if (tutorialState === TutorialState.SendFleet) { return (
Well done! In the Selected Planet pane, you'll see more information about your planet. This pane displays quick information about your planet and the ability to send resources.

Try sending energy to another planet. You can click and drag to look for other planets.
); } else if (tutorialState === TutorialState.SpaceJunk) { return (
When you sent energy to a planet you accumulated some Space Junk. Sending energy to planets that no one has moved to yet will give you junk. You are not allowed to take on more junk than your maximum limit and will be unable to make moves.

Take a look at the top of the screen to see you current and maximum{' '} Space Junk.
tutorialManager.acceptInput(TutorialState.SpaceJunk)}> Next
); } else if (tutorialState === TutorialState.Spaceship) { return (
You also control several space ships - check your home planet! You can move spaceships between any two planets, even if you don't own the source or destination planets. Space ships can move any distance!{' '} Try moving a spaceship you own to another planet now!
); } else if (tutorialState === TutorialState.Deselect) { return (
Congrats, you've submitted a move to xDAI! Moves that are in the mempool are shown as dotted lines. Accepted moves are shown as solid lines.

Try deselecting a planet now. Click in empty space to deselect.
); } else if (tutorialState === TutorialState.ZoomOut) { return (
Great! You'll notice that most of the universe appears grayed out. You need to explore those areas before you can view them.

You'll notice a target indicating where you are currently exploring. Press next when you can see it. You can also zoom using the mouse wheel.
tutorialManager.acceptInput(TutorialState.ZoomOut)}> Next
); } else if (tutorialState === TutorialState.MinerMove) { return (
You can move your explorer by using the bottom-left context menu when nothing is selected.

Try moving your explorer by clicking on the Move button , then clicking somewhere in space.
); } else if (tutorialState === TutorialState.MinerPause) { return (
Great! You can also pause your explorer by clicking the pause {' '} button.

Try pausing your explorer now.
); } else if (tutorialState === TutorialState.Terminal) { return (
You can hide the terminal on the right by clicking on its left edge.

Try hiding the terminal now.
); } else if (tutorialState === TutorialState.HowToGetScore) { return (
It's a Junk War!

Have the highest score at the end of the round to win!
tutorialManager.acceptInput(TutorialState.HowToGetScore)} > Next
); } else if (tutorialState === TutorialState.ScoringDetails) { return (
You can increase your score by withdrawing silver via space time rips, and by finding artifacts. The rarer the artifact, the more points it gives you! You can also increase your score via Capture Zones. Hover over the 'Capture Zone' section in the top bar for more info about capture zones.
tutorialManager.acceptInput(TutorialState.ScoringDetails)} > Next
); } else if (tutorialState === TutorialState.Valhalla) { return (
Winners of each round of Dark Forest v0.6.x will receive a prize, and be added to the{' '} Valhalla universe.

To win, have the highest score (^:
tutorialManager.acceptInput(TutorialState.Valhalla)}> Next
); } else if (tutorialState === TutorialState.AlmostCompleted) { return (
This is the end of the tutorial. Go out and explore the universe! More information will pop up in the upper-right as you discover more about the game.
We hope you enjoy Dark Forest!
tutorialManager.complete()}> Finish
); } else { return <> ; } } const StyledTutorialPane = styled.div<{ visible: boolean }>` display: ${({ visible }) => (visible ? 'block' : 'none')}; position: absolute; top: 0; left: 0; background: ${dfstyles.colors.backgroundlighter}; color: ${dfstyles.colors.text}; padding: 8px; border-bottom: 1px solid ${dfstyles.colors.border}; border-right: 1px solid ${dfstyles.colors.border}; width: 24em; height: fit-content; z-index: 10; & .tutintro { & > div:last-child { display: flex; flex-direction: row; justify-content: space-around; margin-top: 1em; } } & .tutzoom, & .tutalmost { & > div:last-child { display: flex; flex-direction: row; justify-content: flex-end; margin-top: 1em; } } `; export function TutorialPane({ tutorialHook }: { tutorialHook: Hook }) { const uiManager = useUIManager(); const tutorialManager = TutorialManager.getInstance(uiManager); const [tutorialState, setTutorialState] = useState(TutorialState.None); const [tutorialOpen] = tutorialHook; const [completed, setCompleted] = useBooleanSetting(uiManager, Setting.TutorialCompleted); // sync tutorial state useEffect(() => { const update = (newState: TutorialState) => { setTutorialState(newState); setCompleted(newState === TutorialState.Completed); }; tutorialManager.on(TutorialManagerEvent.StateChanged, update); return () => { tutorialManager.removeListener(TutorialManagerEvent.StateChanged, update); }; }, [tutorialManager, setCompleted]); return ( ); } ================================================ FILE: src/Frontend/Panes/TwitterVerifyPane.tsx ================================================ import { RECOMMENDED_MODAL_WIDTH } from '@darkforest_eth/constants'; import { ModalName } from '@darkforest_eth/types'; import React, { useState } from 'react'; import { Btn } from '../Components/Btn'; import { Expand, Spacer } from '../Components/CoreUI'; import { DarkForestTextInput, TextInput } from '../Components/Input'; import { TwitterLink } from '../Components/Labels/Labels'; import { LoadingSpinner } from '../Components/LoadingSpinner'; import { Red } from '../Components/Text'; import { usePlayer, useUIManager } from '../Utils/AppHooks'; import { ModalPane } from '../Views/ModalPane'; import { TabbedView } from '../Views/TabbedView'; export function TwitterVerifyPane({ visible, onClose }: { visible: boolean; onClose: () => void }) { const uiManager = useUIManager(); const user = usePlayer(uiManager); const [twitterHandleInputValue, setTwitterHandleInputValue] = useState(''); const [verifying, setVerifying] = useState(false); const [disconnecting, setDisconnecting] = useState(false); const [error, setError] = useState(false); const onTwitterInputChange = (newHandle: string) => { setTwitterHandleInputValue(newHandle.replace('@', '')); }; /** * Called when the user clicks on the 'tweet' button. Opens up a popup that prompts them to tweet * the required verification tweet from the account that they entered into the pane. */ const onTweetClick = async () => { if (uiManager) { const tweetText = await uiManager.generateVerificationTweet(twitterHandleInputValue); const str = `Verifying my @darkforest_eth v0.6 account (https://zkga.me): ${tweetText}`; window.open(`https://twitter.com/intent/tweet?hashtags=darkforest&text=${encodeURI(str)}`); } }; /** * Called when the user clicks on the 'verify' button. Asks the webserver whether or not the given * twitter account tweeted the correct verification tweet. If that happened, sets the twitter * account internally. */ const onVerifyClick = async () => { try { setVerifying(true); await uiManager?.verifyTwitter(twitterHandleInputValue); } catch (e) { setError(true); } finally { setVerifying(false); } }; /** * Called when the user clicks the 'disconnect' button. Allows them to disconnect their account from twitter. */ const onDisconnectClick = async () => { if (confirm('are you sure you want to disconnect your twitter?')) { try { setDisconnecting(true); await uiManager?.disconnectTwitter(twitterHandleInputValue); } catch (e) { setError(true); } finally { setDisconnecting(false); } } }; return ( {user.value !== undefined && user.value.twitter === undefined && ( { if (i === 0) return ( <> Tweet a signed message, proving account ownership! ) => onTwitterInputChange(e.target.value) } /> Tweet ); if (i === 1) { return ( <> After tweeting, click the button below to verify ownership! ) => onTwitterInputChange(e.target.value) } /> {error && ( <> error verifying ownership )} {verifying ? : 'Verify'} ); } }} /> )} {user.value !== undefined && user.value.twitter && ( <> You are connected, . You can disconnect from twitter anytime by clicking the button below. {verifying ? ( ) : ( 'Disconnect Twitter' )} )} ); } ================================================ FILE: src/Frontend/Panes/UpgradeDetailsPane.tsx ================================================ import { isUnconfirmedUpgradeTx } from '@darkforest_eth/serde'; import { LocationId, Planet, PlanetType, UpgradeBranchName } from '@darkforest_eth/types'; import React from 'react'; import styled from 'styled-components'; import { getPlanetMaxRank, getPlanetRank, isFullRank, upgradeName, } from '../../Backend/Utils/Utils'; import { Btn } from '../Components/Btn'; import { CenterBackgroundSubtext, Spacer } from '../Components/CoreUI'; import { LoadingSpinner } from '../Components/LoadingSpinner'; import { Gold, Red, Sub, Subber } from '../Components/Text'; import { useAccount, usePlanet, useUIManager } from '../Utils/AppHooks'; import { useEmitterValue } from '../Utils/EmitterHooks'; import { ModalHandle } from '../Views/ModalPane'; import { TabbedView } from '../Views/TabbedView'; import { UpgradePreview } from '../Views/UpgradePreview'; const SECTION_MARGIN = '0.75em'; const SectionPreview = styled.div` margin-top: ${SECTION_MARGIN}; `; const SectionBuy = styled.div` margin-top: ${SECTION_MARGIN}; `; export function UpgradeDetailsPaneHelpContent() { return (

Upgrades cost Silver, and allow you to boost the stats of your planet. You need to move the required silver to this planet to be able to spend it on upgrades.

All planets have a certain max rank, and each branch can only be upgraded so many times. Choose wisely!

); } function SilverRequired({ planet }: { planet: Planet }) { const maxRank = getPlanetMaxRank(planet); const silverPerRank = []; for (let i = 0; i < maxRank; i++) { silverPerRank[i] = Math.floor((i + 1) * 0.2 * planet.silverCap); } return ( <> {silverPerRank.map((silver: number, i: number) => ( {i === getPlanetRank(planet) ? {silver} : {silver}} ))} ); } export function UpgradeDetailsPane({ initialPlanetId, modal: _modal, }: { modal: ModalHandle; initialPlanetId: LocationId | undefined; }) { const uiManager = useUIManager(); const planetId = useEmitterValue(uiManager.selectedPlanetId$, initialPlanetId); const planet = usePlanet(uiManager, planetId).value; const account = useAccount(uiManager); const planetAtMaxRank = isFullRank(planet); if (planet && account) { if (planet.owner !== account) { } else if (planet.planetType !== PlanetType.PLANET || planet.silverCap === 0) { return ( This Planet
is not Upgradeable
); } else { return ( { const currentLevel = planet.upgradeState[branch]; const branchAtMaxRank = !planet || planet.upgradeState[branch] >= 4; const upgrade = branchAtMaxRank ? undefined : uiManager.getUpgrade(branch, currentLevel); const totalLevel = planet.upgradeState.reduce((a, b) => a + b); const silverNeeded = Math.floor((totalLevel + 1) * 0.2 * planet.silverCap); const enoughSilver = planet.silver >= silverNeeded; const isPendingUpgrade = planet.transactions?.hasTransaction(isUnconfirmedUpgradeTx); const canUpgrade = enoughSilver && !planetAtMaxRank && !branchAtMaxRank && !isPendingUpgrade; const doUpgrade = (branch: UpgradeBranchName) => { if (canUpgrade) { uiManager.upgrade(planet, branch); } }; return ( <>
Silver Available: {planet.silver}
Silver Cost:
{isPendingUpgrade ? ( ) : ( <> doUpgrade(branch)} disabled={!canUpgrade}> {'Upgrade'} {' '} {planetAtMaxRank ? ( Planet at Max Rank ) : branchAtMaxRank ? ( {upgradeName(branch)} at Max Rank ) : !enoughSilver ? ( Not Enough Silver ) : undefined} )}
); }} /> ); } } return ( Select a Planet
You Own
); } ================================================ FILE: src/Frontend/Panes/WikiPane.tsx ================================================ import React from 'react'; import styled from 'styled-components'; import dfstyles from '../Styles/dfstyles'; export function WikiPane({ children }: { children: React.ReactNode }) { return {children}; } const WikiPaneContainer = styled.div` width: 400px; max-height: 300px; overflow-y: scroll; border-radius: 2px; border: 1px solid ${dfstyles.colors.text}; background-color: ${dfstyles.colors.blueBackground}; color: #ccc; padding: 8px 16px; `; ================================================ FILE: src/Frontend/Panes/ZoomPane.tsx ================================================ import React from 'react'; import styled from 'styled-components'; import { LongDash } from '../Components/Text'; import dfstyles from '../Styles/dfstyles'; import { DFZIndex } from '../Utils/constants'; import UIEmitter, { UIEmitterEvent } from '../Utils/UIEmitter'; const StyledZoomPane = styled.div` z-index: ${DFZIndex.MenuBar}; padding-left: 0.75em; padding-top: 0.1em; margin-top: 0; display: flex; font-size: 1.5em; flex-direction: row; justify-content: flex-end; height: fit-content; & > a:first-child { margin-right: 0.75em; } color: ${dfstyles.colors.subtext}; & > a { &:hover { color: ${dfstyles.colors.text}; cursor: pointer; } &:active { color: ${dfstyles.colors.subbertext}; } } `; export function ZoomPane() { const uiEmitter = UIEmitter.getInstance(); return ( uiEmitter.emit(UIEmitterEvent.ZoomOut)}> uiEmitter.emit(UIEmitterEvent.ZoomIn)}>+ ); } ================================================ FILE: src/Frontend/Renderers/Artifacts/ArtifactRenderer.ts ================================================ import { MAX_BIOME, MIN_BIOME } from '@darkforest_eth/constants'; import { SpriteRenderer, WebGLManager } from '@darkforest_eth/renderer'; import { Artifact, ArtifactRarity, ArtifactType, Biome } from '@darkforest_eth/types'; import autoBind from 'auto-bind'; import { ARTIFACT_ROW_H } from '../../Styles/dfstyles'; const NUM_BIOMES = MAX_BIOME; const thumbDim = 32; const cellDim = ARTIFACT_ROW_H; const mTop = 0.5 * (cellDim - thumbDim); export const artifactColM = 32; export const artifactColW = cellDim * 4; export const aDexCanvasW = 4 * artifactColW + 3 * artifactColM; export const aDexCanvasH = NUM_BIOMES * cellDim; export const aListCanvasW = cellDim; export const aListCanvasH = 400; export class ArtifactRenderer extends WebGLManager { private frameRequestId: number; private spriteRenderer: SpriteRenderer; private visible = false; private artifacts: Artifact[]; private isDex: boolean; private scroll = 0; constructor(canvas: HTMLCanvasElement, isDex = true) { super(canvas); autoBind(this); this.setIsDex(isDex); this.spriteRenderer = new SpriteRenderer(this, true); this.loop(); } public setIsDex(isDex: boolean) { this.isDex = isDex; } public setScroll(scroll: number) { this.scroll = scroll; } public setVisible(visible: boolean): void { this.visible = visible; } public setArtifacts(artifacts: Artifact[]): void { this.artifacts = artifacts; } private queueRarityColumn(rarity: ArtifactRarity, startX: number) { let col = 0; for (let type = 1; type <= 4; type++) { this.queueArtifactColumn(type as ArtifactType, rarity, startX + col * cellDim); col++; } } private containsArtifact(biome: Biome, rarity: ArtifactRarity, type: ArtifactType) { for (const artifact of this.artifacts) { if ( artifact.planetBiome === biome && artifact.artifactType === type && artifact.rarity === rarity ) { return true; } } return false; } private queueArtifactColumn(type: ArtifactType, rarity: ArtifactRarity, startX: number) { let row = 0; for (let biome = MIN_BIOME; biome <= MAX_BIOME; biome++) { const pos = { x: startX, y: mTop + row * ARTIFACT_ROW_H }; const artifact = { planetBiome: biome, rarity, artifactType: type, } as Artifact; const contains = this.containsArtifact(biome, rarity, type); const alpha = contains ? 255 : 70; this.spriteRenderer.queueArtifact(artifact, pos, thumbDim, alpha); row++; } } private drawDex() { for (let r = ArtifactRarity.Common; r <= ArtifactRarity.Legendary; r++) { this.queueRarityColumn(r, r * (artifactColW + artifactColM)); } } private drawList() { let row = 0; for (const artifact of this.artifacts) { const pos = { x: 0, y: row * ARTIFACT_ROW_H + mTop - this.scroll }; this.spriteRenderer.queueArtifact(artifact, pos, thumbDim); row++; } } private draw() { if (this.isDex) this.drawDex(); else this.drawList(); this.spriteRenderer.flush(); } private loop() { if (this.visible) { this.setProjectionMatrix(); this.clear(); this.draw(); } this.frameRequestId = window.requestAnimationFrame(this.loop); } public destroy() { window.cancelAnimationFrame(this.frameRequestId); } } ================================================ FILE: src/Frontend/Renderers/GifRenderer.ts ================================================ import { EMPTY_ARTIFACT_ID, MAX_ARTIFACT_RARITY, MAX_ARTIFACT_TYPE, MAX_BIOME, MIN_ARTIFACT_RARITY, MIN_ARTIFACT_TYPE, MIN_BIOME, } from '@darkforest_eth/constants'; import { ArtifactFileColor, artifactFileName, setForceAncient } from '@darkforest_eth/gamelogic'; import { mockArtifactWithRarity } from '@darkforest_eth/procedural'; import { SpriteRenderer, WebGLManager } from '@darkforest_eth/renderer'; import { Artifact, ArtifactRarity, ArtifactType, Biome } from '@darkforest_eth/types'; import { mat4 } from 'gl-matrix'; import JSZip from 'jszip'; import { GIF_ARTIFACT_COLOR } from '../Pages/GifMaker'; const FileSaver = require('file-saver'); declare global { interface Window { /* eslint-disable @typescript-eslint/no-explicit-any */ CCapture: any; } } const COLORS: Record = { [ArtifactFileColor.BLUE]: [0.0724, 0.051, 0.3111, 1] as const, [ArtifactFileColor.APP_BACKGROUND]: [0.031372549, 0.031372549, 0.031372549, 1] as const, }; export class GifRenderer extends WebGLManager { public projectionMatrix: mat4; private spriteRenderer: SpriteRenderer; private margin: number; private canvasDim: number; private artifactDim: number; private resolution: number; private thumb: boolean; constructor(canvas: HTMLCanvasElement, dim: number, isThumb: boolean) { super(canvas, { preserveDrawingBuffer: true }); this.thumb = isThumb; this.spriteRenderer = new SpriteRenderer(this, isThumb); this.setDim(dim); } private setDim(dim: number) { const SPRITE_DIM = this.thumb ? 16 : 64; this.canvasDim = dim; this.resolution = Math.floor(this.canvasDim / SPRITE_DIM) - 1; this.artifactDim = this.resolution * SPRITE_DIM; this.margin = Math.floor(0.5 * (this.canvasDim - this.artifactDim)); this.setProjectionMatrix(); } // https://gist.github.com/ahgood/bfc57a7f44d6ab7803f3ee2ec0abb980 private drawSprite(artifact: Artifact, atFrame: number | undefined = undefined) { this.clear(); this.spriteRenderer.queueArtifact( artifact, { x: this.margin, y: this.margin }, this.artifactDim, 255, atFrame ); this.spriteRenderer.flush(); } private getBase64(): string { const b64 = this.canvas.toDataURL('image/png').replace(/^data:image\/(png|jpg);base64,/, ''); return b64; } private getFileName( video: boolean, type: ArtifactType, biome: Biome, rarity: ArtifactRarity, ancient: boolean ) { return artifactFileName( video, this.thumb, { artifactType: type, planetBiome: biome, rarity, id: EMPTY_ARTIFACT_ID }, GIF_ARTIFACT_COLOR, { skipCaching: true, forceAncient: ancient } ); } private addSprite( dir: JSZip, type: ArtifactType, biome: Biome, rarity: ArtifactRarity, ancient = false ) { const fileName = this.getFileName(false, type, biome, rarity, ancient); this.drawSprite(mockArtifactWithRarity(rarity, type, biome)); dir.file(fileName, this.getBase64(), { base64: true }); } private async addVideo( dir: JSZip, type: ArtifactType, biome: Biome, rarity: ArtifactRarity, ancient = false ) { const fileName = this.getFileName(true, type, biome, rarity, ancient); const artifact = mockArtifactWithRarity(rarity, type, biome); const capturer = new window.CCapture({ format: 'webm', framerate: 60, quality: 0.999, }); capturer.start(); for (let i = 0; i < 180; i++) { this.drawSprite(artifact, i); capturer.capture(this.canvas); } capturer.stop(); return new Promise((resolve) => { capturer.save((blob: Blob) => { dir.file(fileName, blob); console.log('saved ' + fileName + '!'); resolve(); }); }); } private async addBiomes(videoMode: boolean, dir: JSZip) { setForceAncient(false); for (let type = MIN_ARTIFACT_TYPE; type <= MAX_ARTIFACT_TYPE; type++) { for (let rarity = MIN_ARTIFACT_RARITY; rarity <= MAX_ARTIFACT_RARITY; rarity++) { for (let biome = MIN_BIOME; biome <= MAX_BIOME; biome++) { if (videoMode) await this.addVideo(dir, type, biome, rarity, false); else this.addSprite(dir, type, biome, rarity, false); } } } } private async addAncient(videoMode: boolean, dir: JSZip) { setForceAncient(true); for (let type = MIN_ARTIFACT_TYPE; type <= MAX_ARTIFACT_TYPE; type++) { for (let rarity = MIN_ARTIFACT_RARITY; rarity <= MAX_ARTIFACT_RARITY; rarity++) { if (videoMode) await this.addVideo(dir, type, Biome.OCEAN, rarity, true); else this.addSprite(dir, type, Biome.OCEAN, rarity, true); } } } private async getAll(videoMode = false) { const zip = new JSZip(); zip.folder('img'); const dir = zip.folder('img'); if (!dir) { console.error('jszip error'); return; } await this.addBiomes(videoMode, dir); await this.addAncient(videoMode, dir); zip.generateAsync({ type: 'blob' }).then((content) => { FileSaver.saveAs(content, 'files.zip'); }); } getAllSprites() { this.getAll(false); } getAllVideos() { this.getAll(true); } clear() { super.clear(0, [...COLORS[GIF_ARTIFACT_COLOR]]); } } ================================================ FILE: src/Frontend/Renderers/LandingPageCanvas.tsx ================================================ import autoBind from 'auto-bind'; import React, { RefObject, useCallback, useEffect, useRef } from 'react'; import dfstyles from '../Styles/dfstyles'; import { hasTouchscreen, isMobileOrTablet } from '../Utils/BrowserChecks'; const canvasStyle = { position: 'absolute', width: '100vw', height: '150vh', } as React.CSSProperties; type Position = { x: number; y: number }; type Point = { pos: Position; r: number; z: number; color: string; }; type Edge = [number, number]; // as index, index class LandingPageCanvasRenderer { static instance: LandingPageCanvasRenderer | null; canvasRef: RefObject; canvas: HTMLCanvasElement; ctx: CanvasRenderingContext2D; frameRequestId: number; mouse: Position; delMouse: Position; points: Point[]; realPos: Position[]; edges: Edge[]; private constructor(canvas: HTMLCanvasElement) { this.canvas = canvas; const ctx = this.canvas.getContext('2d'); if (!ctx) { throw new Error('Not a 2D canvas.'); } this.ctx = ctx; this.mouse = { x: -0xdeadbeef, y: -0xdeadbeef }; this.delMouse = { x: 0, y: 0 }; this.points = []; this.edges = []; autoBind(this); this.setupDraw(); this.frame(); } private makeConstellation(center: Position): void { const randomColor: () => string = () => { const idx = Math.floor(Math.random() * 3); return [dfstyles.colors.dfgreen, dfstyles.colors.dfblue, dfstyles.colors.dfred][idx]; }; const newPoints: Point[] = []; let newEdges: Edge[] = []; const numPoints = Math.floor(2 + Math.random() * 7); for (let i = 0; i < numPoints; i++) { const size = 20 + Math.random() * 160; const t = Math.random() * 2 * Math.PI; newPoints.push({ pos: { x: center.x + size * Math.sin(t), y: center.y + size * Math.cos(t), }, z: 0.2 + Math.random() * 0.8, r: 1 + Math.random() * 4, color: randomColor(), }); } const numEdges = Math.floor((1 + Math.random()) * (numPoints - 1)); for (let i = 0; i < numEdges; i++) { const p1 = Math.floor(Math.random() * numPoints); let p2 = p1; while (p2 === p1) p2 = Math.floor(Math.random() * numPoints); newEdges.push([p1, p2]); } const length = this.points.length; newEdges = newEdges.map((e) => [e[0] + length, e[1] + length]); for (const point of newPoints) { this.points.push(point); this.realPos.push({ x: -0xdeadbeef, y: -0xdeadbeef, }); } for (const edge of newEdges) { this.edges.push(edge); } } private setupDraw() { const canvas = this.canvas; this.points = []; this.edges = []; this.realPos = []; this.canvas.width = this.canvas.getBoundingClientRect().width; this.canvas.height = this.canvas.getBoundingClientRect().height; const numH = window.innerWidth > 400 ? Math.floor(canvas.width / 200) : 2; const numV = window.innerHeight > 600 ? Math.floor(canvas.height / 360) : 3; const perturb = () => Math.random() * 100 - 50; const midX = (x: number) => 0.2 * canvas.width < x && x < 0.8 * canvas.width; const midY = (y: number) => 0.2 * canvas.height < y && y < 0.8 * canvas.height; const mid = (x: number, y: number) => midX(x) && midY(y); for (let i = 0; i < numH; i++) { for (let j = 0; j < numV; j++) { const baseX = (i * canvas.width) / (numH - 1); const baseY = (j * canvas.height) / 2 / (numV - 1); const rand = Math.random(); const m = mid(baseX, baseY); if ((m && rand < 0.4) || (!m && rand < 0.9)) { this.makeConstellation({ x: baseX + perturb(), y: baseY + perturb(), }); } } } } private allowMouse(): boolean { return !(hasTouchscreen() || isMobileOrTablet() || window.innerWidth < 600); } private frame(): void { // references for ease const ctx = this.ctx; const canvas = this.canvas; const mouse = this.mouse; const delMouse = this.delMouse; const edges = this.edges; const points = this.points; const realPos = this.realPos; // fake mouse if (!this.allowMouse()) { const now = () => Date.now() / 3000; const xOfT = () => 1.5 * (0.5 + 0.5 * Math.cos(now())); const yOfT = () => 1.5 * (0.5 + 0.5 * Math.sin(now())); const fakeE = { clientX: xOfT() * window.innerWidth, clientY: yOfT() * window.innerHeight, }; this._mouseMove(fakeE as MouseEvent); } // draw stuff ctx.clearRect(0, 0, canvas.width, canvas.height); for (const i in points) { const point = points[i]; // const { x, y } = point.pos; const { z, pos: { x, y }, } = point; const realX = x + z * delMouse.x * -4; const realY = y + z * delMouse.y * -4; this.realPos[i] = { x: realX, y: realY, }; } for (const edge of edges) { const [i1, i2] = edge; const p1 = realPos[i1].x === -0xdeadbeef ? points[i1].pos : realPos[i1]; const p2 = realPos[i2].x === -0xdeadbeef ? points[i2].pos : realPos[i2]; ctx.strokeStyle = dfstyles.colors.subtext; ctx.lineWidth = 0.5; ctx.beginPath(); ctx.moveTo(p1.x, p1.y); ctx.lineTo(p2.x, p2.y); ctx.stroke(); } for (const i in points) { const { x: realX, y: realY } = this.realPos[i]; const point = points[i]; // const { x, y } = point.pos; const { r, color } = point; const dist = (realX - mouse.x) ** 2 + (realY - mouse.y) ** 2; const maxDist = 900 ** 2; const factor1 = Math.max(0, 1 - dist / maxDist); const factor2 = 0.5 * (Math.sin(8 ** Math.sin(dist / 300000)) + 1); // const factor = factor1 * factor2; const factor = factor1 * factor2; // const factor = factor1; ctx.fillStyle = dfstyles.colors.subtext; ctx.beginPath(); ctx.arc(realX, realY, r * (1 + factor * 1.5), 0, 2 * Math.PI); ctx.fill(); ctx.globalAlpha = factor; ctx.fillStyle = color; ctx.beginPath(); ctx.arc(realX, realY, r * (1 + factor * 1.5), 0, 2 * Math.PI); ctx.fill(); ctx.globalAlpha = 1.0; } this.frameRequestId = window.requestAnimationFrame(this.frame.bind(this)); } private mouseMove(e: MouseEvent) { if (!this.allowMouse()) return; this._mouseMove(e); } private _mouseMove(e: MouseEvent) { const newMouse = { x: e.clientX, y: e.clientY, }; let vMouse = { x: 0, y: 0 }; const _tCircle: (base: Position, max: number) => Position = (base, max) => { if (base.x ** 2 + base.y ** 2 > max ** 2) { const mult = max / Math.sqrt(base.x ** 2 + base.y ** 2); return { x: base.x * mult, y: base.y * mult }; } else return base; }; const _tIdentity = (x: Position, _m: number) => x; if (this.mouse.x !== -0xdeadbeef) { const baseVmouse = { x: newMouse.x - this.mouse.x, y: newMouse.y - this.mouse.y, }; vMouse = _tCircle(baseVmouse, 20); } const baseDelMouse = { x: this.delMouse.x + vMouse.x * 0.1, y: this.delMouse.y + vMouse.y * 0.1, }; this.delMouse = _tIdentity(baseDelMouse, 100); this.mouse = { x: e.clientX, y: e.clientY, }; } static initialize(canvas: HTMLCanvasElement) { const canvasRenderer = new LandingPageCanvasRenderer(canvas); LandingPageCanvasRenderer.instance = canvasRenderer; const _this = canvasRenderer; window.addEventListener('mousemove', _this.mouseMove); return canvasRenderer; } static destroyInstance(): void { const _this = LandingPageCanvasRenderer.instance; if (_this) { window.cancelAnimationFrame(_this.frameRequestId); window.removeEventListener('mousemove', _this.mouseMove); } LandingPageCanvasRenderer.instance = null; } } export default function LandingPageCanvas() { const [width, setWidth] = React.useState(window.innerWidth); const [height, setHeight] = React.useState(window.innerHeight * 1.5); const canvasRef = useRef(null); const onResize = useCallback(function onResize() { setWidth(window.innerWidth); setHeight(window.innerHeight); }, []); useEffect(() => { if (canvasRef.current) LandingPageCanvasRenderer.initialize(canvasRef.current); else console.error('could not init draw'); window.addEventListener('resize', onResize); return () => { window.removeEventListener('resize', onResize); LandingPageCanvasRenderer.destroyInstance(); }; }, [onResize]); return ; } export function LandingPageBackground() { return (
); } ================================================ FILE: src/Frontend/Renderers/PlanetscapeRenderer/PlanetIcons.tsx ================================================ import { EMPTY_ADDRESS, MAX_PLANET_LEVEL } from '@darkforest_eth/constants'; import { isLocatable } from '@darkforest_eth/gamelogic'; import { bonusFromHex } from '@darkforest_eth/hexgen'; import { getPlanetName } from '@darkforest_eth/procedural'; import { Planet, PlanetType, TooltipName } from '@darkforest_eth/types'; import React from 'react'; import styled from 'styled-components'; import { getPlanetRank } from '../../../Backend/Utils/Utils'; import { StatIdx } from '../../../_types/global/GlobalTypes'; import { Icon, IconType, RankIcon } from '../../Components/Icons'; import { TooltipTrigger } from '../../Panes/Tooltip'; import dfstyles from '../../Styles/dfstyles'; import { useUIManager } from '../../Utils/AppHooks'; const StyledPlanetIcons = styled.div` display: inline-flex; flex-direction: row; flex-wrap: wrap-reverse; align-items: center; justify-content: center; & > span { font-size: 0.9em; width: 1.5em; height: 1.5em; background: ${dfstyles.colors.backgroundlighter}; border-radius: 2px; margin: 0.1em; margin-right: 0.25em; cursor: help; &, & > span { display: inline-flex !important; flex-direction: row; justify-content: space-around; align-items: center; } } `; const ClownIcon = styled.span` background: red; width: 8px; height: 8px; border-radius: 4px; `; export function PlanetIcons({ planet }: { planet: Planet | undefined }) { const uiManager = useUIManager(); if (!planet) return ; const bonuses = bonusFromHex(planet.locationId); const rank = getPlanetRank(planet); let captureZoneIcons = null; if (uiManager.captureZonesEnabled) { const captureZoneGenerator = uiManager.getCaptureZoneGenerator(); if (captureZoneGenerator) { captureZoneIcons = ( <> {captureZoneGenerator.isInZone(planet.locationId) && uiManager.potentialCaptureScore(planet.planetLevel) > 0 && planet.invader === EMPTY_ADDRESS && planet.capturer === EMPTY_ADDRESS && ( )} {planet.invader !== EMPTY_ADDRESS && planet.capturer === EMPTY_ADDRESS && ( )} {planet.capturer !== EMPTY_ADDRESS && ( This planet has been captured by {planet.capturer}} > )} ); } } return ( {planet.owner === EMPTY_ADDRESS && planet.energy > 0 && ( )} {planet.planetLevel === MAX_PLANET_LEVEL && ( )} {planet.planetType === PlanetType.SILVER_MINE && ( )} {bonuses[StatIdx.EnergyCap] && ( )} {bonuses[StatIdx.EnergyGro] && ( )} {bonuses[StatIdx.Range] && ( )} {bonuses[StatIdx.Speed] && ( )} {bonuses[StatIdx.Defense] && ( )} {bonuses[StatIdx.SpaceJunk] && ( )} {rank > 0 && ( )} {getPlanetName(planet) === 'Clown Town' && ( )} {isLocatable(planet) && planet.planetType === PlanetType.RUINS && !planet.hasTriedFindingArtifact && ( )} {captureZoneIcons} {planet.destroyed && ( This planet is destroyed. It does not generate energy or silver, all incoming voyages are void, and you cannot send or receive energy from it. } > )} ); } ================================================ FILE: src/Frontend/Styles/Colors.tsx ================================================ import { ArtifactRarity, Biome } from '@darkforest_eth/types'; import dfstyles from './dfstyles'; /* tsx file so that we get color previews in VScode! */ export const BiomeTextColors = { [Biome.UNKNOWN]: '#000000', [Biome.OCEAN]: '#0088ff', [Biome.FOREST]: '#46FB73', [Biome.GRASSLAND]: '#CFF391', [Biome.TUNDRA]: '#FB6A9D', [Biome.SWAMP]: '#b48812', [Biome.DESERT]: '#ffe554', [Biome.ICE]: 'hsl(198, 78%, 77%)', [Biome.WASTELAND]: '#000000', [Biome.LAVA]: '#FF5100', [Biome.CORRUPTED]: '#8DF15B', } as const; export const BiomeBackgroundColors = { [Biome.UNKNOWN]: '#000000', [Biome.OCEAN]: '#000e2d', [Biome.FOREST]: '#06251d', [Biome.GRASSLAND]: '#212617', [Biome.TUNDRA]: '#260f17', [Biome.SWAMP]: '#211b0e', [Biome.DESERT]: '#302e0e', [Biome.ICE]: '#0d212f', [Biome.WASTELAND]: '#321b1b', [Biome.LAVA]: '#321000', [Biome.CORRUPTED]: '#15260D', } as const; export const ANCIENT_PURPLE = '#d23191'; export const ANCIENT_BLUE = '#b2fffc'; export const RarityColors = { [ArtifactRarity.Unknown]: '#000000', [ArtifactRarity.Common]: dfstyles.colors.subtext, [ArtifactRarity.Rare]: '#6b68ff', [ArtifactRarity.Epic]: '#c13cff', [ArtifactRarity.Legendary]: '#f8b73e', [ArtifactRarity.Mythic]: '#ff44b7', } as const; ================================================ FILE: src/Frontend/Styles/Mixins.tsx ================================================ import { isLocatable } from '@darkforest_eth/gamelogic'; import { Planet, PlanetType } from '@darkforest_eth/types'; import { css, keyframes } from 'styled-components'; import { BiomeBackgroundColors } from './Colors'; const scrolling = keyframes` from { background-position: 0 0; } to { background-position: 200px 200px; } `; export function planetBackground({ planet }: { planet: Planet | undefined }) { if (!planet || planet.planetType === PlanetType.TRADING_POST) return css` background: url('/public/img/spacebg.jpg'); background-size: 200px 200px; background-repeat: repeat; animation: ${scrolling} 10s linear infinite; `; else return isLocatable(planet) ? css` background: ${BiomeBackgroundColors[planet.biome]}; ` : ``; } ================================================ FILE: src/Frontend/Styles/dfstyles.ts ================================================ import { RECOMMENDED_MODAL_WIDTH } from '@darkforest_eth/constants'; import { SpaceType } from '@darkforest_eth/types'; import color from 'color'; import { css } from 'styled-components'; export const ARTIFACT_ROW_H = 48; export const SPACE_TYPE_COLORS = { [SpaceType.NEBULA]: 'rgb(0, 20.4, 81.6)', [SpaceType.SPACE]: 'rgb(0, 5.4, 43.35)', [SpaceType.DEEP_SPACE]: 'rgb(2.04, 0, 6.12)', [SpaceType.DEAD_SPACE]: 'rgb(0, 37, 1)', } as const; const text = color('#bbb').hex(); const textLight = color(text).lighten(0.3).hex(); const subtext = color(text).darken(0.3).hex(); const subbertext = color(text).darken(0.5).hex(); const subbesttext = color(text).darken(0.8).hex(); const background = '#151515'; const backgrounddark = '#252525'; const backgroundlight = color(background).lighten(0.5).hex(); const backgroundlighter = color(backgroundlight).lighten(0.3).hex(); const border = '#777'; const borderDark = color(border).darken(0.2).hex(); const borderDarker = color(borderDark).darken(0.2).hex(); const borderDarkest = color(borderDarker).darken(0.5).hex(); const blueBackground = '#0a0a23'; const dfblue = '#00ADE1'; const dfgreen = '#00DC82'; const dfgreendark = color(dfgreen).darken(0.7).hex(); const dfgreenlight = color(dfgreen).lighten(0.1).hex(); const dfred = '#FF6492'; const dfyellow = '#e8e228'; const dfpurple = '#9189d9'; const dfwhite = '#ffffff'; const dforange = 'rgb(196, 101, 0)'; const dfstyles = { colors: { text, textLight, subtext, subbertext, subbesttext, blueBackground, background, backgrounddark, backgroundlight, backgroundlighter, dfblue, border, borderDark, borderDarker, borderDarkest, dfgreen, dfgreendark, dfgreenlight, dfred, dfyellow, dfpurple, dfwhite, dforange, artifactBackground: 'rgb(21, 17, 71)', icons: { twitter: '#1DA1F2', github: '#8e65db', discord: '#7289da', email: '#D44638', blog: '#ffcb1f', }, }, borderRadius: '3px', fontSize: '16pt', fontSizeS: '12pt', fontSizeXS: '10pt', fontH1: '42pt', fontH1S: '36pt', fontH2: '24pt', titleFont: 'perfect_dos_vga_437regular', screenSizeS: '660px', game: { terminalWidth: '240pt', fontSize: '12pt', canvasbg: '#100544', rangecolors: { dash: '#9691bf', dashenergy: '#f5c082', colorenergy: '#080330', color100: '#050228', color50: '#050233', color25: '#050238', }, bonuscolors: { energyCap: 'hsl(360, 73%, 70%)', speed: 'hsl(290, 73%, 70%)', def: 'hsl(231, 73%, 70%)', spaceJunk: 'hsl(43, 33%, 29%)', energyGro: 'hsl(136, 73%, 70%)', range: 'hsl(50, 73%, 70%)', }, toolbarHeight: '12em', terminalFontSize: '10pt', styles: { active: 'filter: brightness(80%)', animProps: 'ease-in-out infinite alternate-reverse', }, }, prefabs: { // https://stackoverflow.com/questions/826782/how-to-disable-text-selection-highlighting noselect: css` -webkit-touch-callout: none; /* iOS Safari */ -webkit-user-select: none; /* Safari */ -khtml-user-select: none; /* Konqueror HTML */ -moz-user-select: none; /* Old versions of Firefox */ -ms-user-select: none; /* Internet Explorer/Edge */ user-select: none; `, }, }; export const snips = { bigPadding: css` padding: 2px 12px; `, defaultModalWidth: css` width: ${RECOMMENDED_MODAL_WIDTH}; max-width: ${RECOMMENDED_MODAL_WIDTH}; `, defaultBackground: `background: ${dfstyles.colors.background};`, roundedBorders: `border-radius:${dfstyles.borderRadius};`, roundedBordersWithEdge: css` border-radius: 3px; border: 1px solid ${dfstyles.colors.borderDark}; `, absoluteTopLeft: css` position: absolute; top: 0; left: 0; `, pane: ``, // It is unclear where this should go in this file destroyedBackground: { backgroundImage: 'url("/public/img/destroyedbg.png")', backgroundSize: '150px', backgroundPosition: 'right bottom', backgroundRepeat: 'no-repeat', } as CSSStyleDeclaration & React.CSSProperties, }; export default dfstyles; ================================================ FILE: src/Frontend/Styles/font/generator_config.txt ================================================ # Font Squirrel Font-face Generator Configuration File # Upload this file to the generator to recreate the settings # you used to create these fonts. {"mode":"optimal","formats":["woff","woff2"],"tt_instructor":"default","fix_gasp":"xy","fix_vertical_metrics":"Y","metrics_ascent":"","metrics_descent":"","metrics_linegap":"","add_spaces":"Y","add_hyphens":"Y","fallback":"none","fallback_custom":"100","options_subset":"basic","subset_custom":"","subset_custom_range":"","subset_ot_features_list":"","css_stylesheet":"stylesheet.css","filename_suffix":"-webfont","emsquare":"2048","spacing_adjustment":"0"} ================================================ FILE: src/Frontend/Styles/font/perfect_dos_vga_437-demo.html ================================================ Perfect DOS VGA 437 Regular Specimen
AaBb
A​B​C​D​E​F​G​H​I​J​K​L​M​N​O​P​Q​R​S​T​U​V​W​X​Y​Z​a​b​c​d​e​f​g​h​i​j​k​l​m​n​o​p​q​r​s​t​u​v​w​x​y​z​1​2​3​4​5​6​7​8​9​0​&​.​,​?​!​@​(​)​#​$​%​*​+​-​=​:​;
10 abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ
11 abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ
12 abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ
13 abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ
14 abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ
16 abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ
18 abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ
20 abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ
24 abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ
30 abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ
36 abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ
48 abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ
60 abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ
72 abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ
90 abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ
◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼body
body
body
body
bodyPerfect DOS VGA 437 Regular
bodyArial
bodyVerdana
bodyGeorgia

10.Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.

11.Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.

12.Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.

13.Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.

14.Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.

16.Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.

18.Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.

20.Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.

24.Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.

30.Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.

10.Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.

11.Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.

12.Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.

13.Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.

14.Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.

16.Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.

18.Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.

20.Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.

24.Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.

30.Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.

Lorem Ipsum Dolor

Etiam porta sem malesuada magna mollis euismod

Donec sed odio dui. Morbi leo risus, porta ac consectetur ac, vestibulum at eros. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus.

Pellentesque ornare sem

Maecenas sed diam eget risus varius blandit sit amet non magna. Maecenas faucibus mollis interdum. Donec ullamcorper nulla non metus auctor fringilla. Nullam id dolor id nibh ultricies vehicula ut id elit. Nullam id dolor id nibh ultricies vehicula ut id elit.

Aenean eu leo quam. Pellentesque ornare sem lacinia quam venenatis vestibulum. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus.

Nulla vitae elit libero, a pharetra augue. Praesent commodo cursus magna, vel scelerisque nisl consectetur et. Aenean lacinia bibendum nulla sed consectetur.

Nullam quis risus eget urna mollis ornare vel eu leo. Nullam quis risus eget urna mollis ornare vel eu leo. Maecenas sed diam eget risus varius blandit sit amet non magna. Donec ullamcorper nulla non metus auctor fringilla.

Cras mattis consectetur

Aenean eu leo quam. Pellentesque ornare sem lacinia quam venenatis vestibulum. Aenean lacinia bibendum nulla sed consectetur. Integer posuere erat a ante venenatis dapibus posuere velit aliquet. Cras mattis consectetur purus sit amet fermentum.

Nullam id dolor id nibh ultricies vehicula ut id elit. Nullam quis risus eget urna mollis ornare vel eu leo. Cras mattis consectetur purus sit amet fermentum.

Language Support

The subset of Perfect DOS VGA 437 Regular in this kit supports the following languages:
Albanian, Basque, Breton, Chamorro, Danish, Dutch, English, Faroese, Finnish, French, Frisian, Galician, German, Icelandic, Italian, Malagasy, Norwegian, Portuguese, Spanish, Alsatian, Aragonese, Arapaho, Arrernte, Asturian, Aymara, Bislama, Cebuano, Corsican, Fijian, French_creole, Genoese, Gilbertese, Greenlandic, Haitian_creole, Hiligaynon, Hmong, Hopi, Ibanag, Iloko_ilokano, Indonesian, Interglossa_glosa, Interlingua, Irish_gaelic, Jerriais, Lojban, Lombard, Luxembourgeois, Manx, Mohawk, Norfolk_pitcairnese, Occitan, Oromo, Pangasinan, Papiamento, Piedmontese, Potawatomi, Rhaeto-romance, Romansh, Rotokas, Sami_lule, Samoan, Sardinian, Scots_gaelic, Seychelles_creole, Shona, Sicilian, Somali, Southern_ndebele, Swahili, Swati_swazi, Tagalog_filipino_pilipino, Tetum, Tok_pisin, Uyghur_latinized, Volapuk, Walloon, Warlpiri, Xhosa, Yapese, Zulu, Latinbasic, Ubasic, Demo

Glyph Chart

The subset of Perfect DOS VGA 437 Regular in this kit includes all the glyphs listed below. Unicode entities are included above each glyph to help you insert individual characters into your layout.

&#1;



&#2;



&#3;



&#4;



&#5;



&#6;



&#7;



&#8;



&#9;

&#10;

&#11;

&#12;

&#13;

&#14;



&#15;



&#16;



&#17;



&#18;



&#19;



&#20;



&#21;



&#22;



&#23;



&#24;



&#25;



&#26;



&#27;



&#28;



&#29;



&#30;



&#31;



&#32;

&#33;

!

&#34;

"

&#35;

#

&#36;

$

&#37;

%

&#38;

&

&#39;

'

&#40;

(

&#41;

)

&#42;

*

&#43;

+

&#44;

,

&#45;

-

&#46;

.

&#47;

/

&#48;

0

&#49;

1

&#50;

2

&#51;

3

&#52;

4

&#53;

5

&#54;

6

&#55;

7

&#56;

8

&#57;

9

&#58;

:

&#59;

;

&#60;

<

&#61;

=

&#62;

>

&#63;

?

&#64;

@

&#65;

A

&#66;

B

&#67;

C

&#68;

D

&#69;

E

&#70;

F

&#71;

G

&#72;

H

&#73;

I

&#74;

J

&#75;

K

&#76;

L

&#77;

M

&#78;

N

&#79;

O

&#80;

P

&#81;

Q

&#82;

R

&#83;

S

&#84;

T

&#85;

U

&#86;

V

&#87;

W

&#88;

X

&#89;

Y

&#90;

Z

&#91;

[

&#92;

\

&#93;

]

&#94;

^

&#95;

_

&#96;

`

&#97;

a

&#98;

b

&#99;

c

&#100;

d

&#101;

e

&#102;

f

&#103;

g

&#104;

h

&#105;

i

&#106;

j

&#107;

k

&#108;

l

&#109;

m

&#110;

n

&#111;

o

&#112;

p

&#113;

q

&#114;

r

&#115;

s

&#116;

t

&#117;

u

&#118;

v

&#119;

w

&#120;

x

&#121;

y

&#122;

z

&#123;

{

&#124;

|

&#125;

}

&#126;

~

&#127;



&#129;



&#141;



&#142;

Ž

&#143;



&#144;



&#157;



&#158;

ž

&#160;

 

&#161;

¡

&#162;

¢

&#163;

£

&#164;

¤

&#165;

¥

&#166;

¦

&#167;

§

&#168;

¨

&#169;

©

&#170;

ª

&#171;

«

&#172;

¬

&#173;

­

&#174;

®

&#175;

¯

&#176;

°

&#177;

±

&#178;

²

&#179;

³

&#180;

´

&#181;

µ

&#182;

&#183;

·

&#184;

¸

&#185;

¹

&#186;

º

&#187;

»

&#188;

¼

&#189;

½

&#190;

¾

&#191;

¿

&#192;

À

&#193;

Á

&#194;

Â

&#195;

Ã

&#196;

Ä

&#197;

Å

&#198;

Æ

&#199;

Ç

&#200;

È

&#201;

É

&#202;

Ê

&#203;

Ë

&#204;

Ì

&#205;

Í

&#206;

Î

&#207;

Ï

&#208;

Ð

&#209;

Ñ

&#210;

Ò

&#211;

Ó

&#212;

Ô

&#213;

Õ

&#214;

Ö

&#215;

×

&#216;

Ø

&#217;

Ù

&#218;

Ú

&#219;

Û

&#220;

Ü

&#221;

Ý

&#222;

Þ

&#223;

ß

&#224;

à

&#225;

á

&#226;

â

&#227;

ã

&#228;

ä

&#229;

å

&#230;

æ

&#231;

ç

&#232;

è

&#233;

é

&#234;

ê

&#235;

ë

&#236;

ì

&#237;

í

&#238;

î

&#239;

ï

&#240;

ð

&#241;

ñ

&#242;

ò

&#243;

ó

&#244;

ô

&#245;

õ

&#246;

ö

&#247;

÷

&#248;

ø

&#249;

ù

&#250;

ú

&#251;

û

&#252;

ü

&#253;

ý

&#254;

þ

&#255;

ÿ

&#338;

Œ

&#339;

œ

&#376;

Ÿ

&#710;

ˆ

&#732;

˜

&#8192;

 

&#8193;

&#8194;

&#8195;

&#8196;

&#8197;

&#8198;

&#8199;

&#8200;

&#8201;

&#8202;

&#8208;

&#8209;

&#8210;

&#8211;

&#8212;

&#8216;

&#8217;

&#8218;

&#8220;

&#8221;

&#8222;

&#8226;

&#8230;

&#8239;

&#8249;

&#8250;

&#8287;

&#8364;

&#8482;

&#9724;

Installing Webfonts

Webfonts are supported by all major browser platforms but not all in the same way. There are currently four different font formats that must be included in order to target all browsers. This includes TTF, WOFF, EOT and SVG.

1. Upload your webfonts

You must upload your webfont kit to your website. They should be in or near the same directory as your CSS files.

2. Include the webfont stylesheet

A special CSS @font-face declaration helps the various browsers select the appropriate font it needs without causing you a bunch of headaches. Learn more about this syntax by reading the Fontspring blog post about it. The code for it is as follows:

@font-face{ font-family: 'MyWebFont'; src: url('WebFont.eot'); src: url('WebFont.eot?#iefix') format('embedded-opentype'), url('WebFont.woff') format('woff'), url('WebFont.ttf') format('truetype'), url('WebFont.svg#webfont') format('svg'); }

We've already gone ahead and generated the code for you. All you have to do is link to the stylesheet in your HTML, like this:

<link rel="stylesheet" href="stylesheet.css" type="text/css" charset="utf-8" />

3. Modify your own stylesheet

To take advantage of your new fonts, you must tell your stylesheet to use them. Look at the original @font-face declaration above and find the property called "font-family." The name linked there will be what you use to reference the font. Prepend that webfont name to the font stack in the "font-family" property, inside the selector you want to change. For example:

p { font-family: 'WebFont', Arial, sans-serif; }

4. Test

Getting webfonts to work cross-browser can be tricky. Use the information in the sidebar to help you if you find that fonts aren't loading in a particular browser.

================================================ FILE: src/Frontend/Styles/font/specimen_files/grid_12-825-55-15.css ================================================ /*Notes about grid: Columns: 12 Grid Width: 825px Column Width: 55px Gutter Width: 15px -------------------------------*/ .section { margin-bottom: 18px; } .section:after { content: '.'; display: block; height: 0; clear: both; visibility: hidden; } .section { *zoom: 1; } .section .firstcolumn, .section .firstcol { margin-left: 0; } /* Border on left hand side of a column. */ .border { padding-left: 7px; margin-left: 7px; border-left: 1px solid #eee; } /* Border with more whitespace, spans one column. */ .colborder { padding-left: 42px; margin-left: 42px; border-left: 1px solid #eee; } /* The Grid Classes */ .grid1, .grid1_2cols, .grid1_3cols, .grid1_4cols, .grid2, .grid2_3cols, .grid2_4cols, .grid3, .grid3_2cols, .grid3_4cols, .grid4, .grid4_3cols, .grid5, .grid5_2cols, .grid5_3cols, .grid5_4cols, .grid6, .grid6_4cols, .grid7, .grid7_2cols, .grid7_3cols, .grid7_4cols, .grid8, .grid8_3cols, .grid9, .grid9_2cols, .grid9_4cols, .grid10, .grid10_3cols, .grid10_4cols, .grid11, .grid11_2cols, .grid11_3cols, .grid11_4cols, .grid12 { margin-left: 15px; float: left; display: inline; overflow: hidden; } .width1, .grid1, .span-1 { width: 55px; } .width1_2cols, .grid1_2cols { width: 20px; } .width1_3cols, .grid1_3cols { width: 8px; } .width1_4cols, .grid1_4cols { width: 2px; } .input_width1 { width: 49px; } .width2, .grid2, .span-2 { width: 125px; } .width2_3cols, .grid2_3cols { width: 31px; } .width2_4cols, .grid2_4cols { width: 20px; } .input_width2 { width: 119px; } .width3, .grid3, .span-3 { width: 195px; } .width3_2cols, .grid3_2cols { width: 90px; } .width3_4cols, .grid3_4cols { width: 37px; } .input_width3 { width: 189px; } .width4, .grid4, .span-4 { width: 265px; } .width4_3cols, .grid4_3cols { width: 78px; } .input_width4 { width: 259px; } .width5, .grid5, .span-5 { width: 335px; } .width5_2cols, .grid5_2cols { width: 160px; } .width5_3cols, .grid5_3cols { width: 101px; } .width5_4cols, .grid5_4cols { width: 72px; } .input_width5 { width: 329px; } .width6, .grid6, .span-6 { width: 405px; } .width6_4cols, .grid6_4cols { width: 90px; } .input_width6 { width: 399px; } .width7, .grid7, .span-7 { width: 475px; } .width7_2cols, .grid7_2cols { width: 230px; } .width7_3cols, .grid7_3cols { width: 148px; } .width7_4cols, .grid7_4cols { width: 107px; } .input_width7 { width: 469px; } .width8, .grid8, .span-8 { width: 545px; } .width8_3cols, .grid8_3cols { width: 171px; } .input_width8 { width: 539px; } .width9, .grid9, .span-9 { width: 615px; } .width9_2cols, .grid9_2cols { width: 300px; } .width9_4cols, .grid9_4cols { width: 142px; } .input_width9 { width: 609px; } .width10, .grid10, .span-10 { width: 685px; } .width10_3cols, .grid10_3cols { width: 218px; } .width10_4cols, .grid10_4cols { width: 160px; } .input_width10 { width: 679px; } .width11, .grid11, .span-11 { width: 755px; } .width11_2cols, .grid11_2cols { width: 370px; } .width11_3cols, .grid11_3cols { width: 241px; } .width11_4cols, .grid11_4cols { width: 177px; } .input_width11 { width: 749px; } .width12, .grid12, .span-12 { width: 825px; } .input_width12 { width: 819px; } /* Subdivided grid spaces */ .emptycols_left1, .prepend-1 { padding-left: 70px; } .emptycols_right1, .append-1 { padding-right: 70px; } .emptycols_left2, .prepend-2 { padding-left: 140px; } .emptycols_right2, .append-2 { padding-right: 140px; } .emptycols_left3, .prepend-3 { padding-left: 210px; } .emptycols_right3, .append-3 { padding-right: 210px; } .emptycols_left4, .prepend-4 { padding-left: 280px; } .emptycols_right4, .append-4 { padding-right: 280px; } .emptycols_left5, .prepend-5 { padding-left: 350px; } .emptycols_right5, .append-5 { padding-right: 350px; } .emptycols_left6, .prepend-6 { padding-left: 420px; } .emptycols_right6, .append-6 { padding-right: 420px; } .emptycols_left7, .prepend-7 { padding-left: 490px; } .emptycols_right7, .append-7 { padding-right: 490px; } .emptycols_left8, .prepend-8 { padding-left: 560px; } .emptycols_right8, .append-8 { padding-right: 560px; } .emptycols_left9, .prepend-9 { padding-left: 630px; } .emptycols_right9, .append-9 { padding-right: 630px; } .emptycols_left10, .prepend-10 { padding-left: 700px; } .emptycols_right10, .append-10 { padding-right: 700px; } .emptycols_left11, .prepend-11 { padding-left: 770px; } .emptycols_right11, .append-11 { padding-right: 770px; } .pull-1 { margin-left: -70px; } .push-1 { margin-right: -70px; margin-left: 18px; float: right; } .pull-2 { margin-left: -140px; } .push-2 { margin-right: -140px; margin-left: 18px; float: right; } .pull-3 { margin-left: -210px; } .push-3 { margin-right: -210px; margin-left: 18px; float: right; } .pull-4 { margin-left: -280px; } .push-4 { margin-right: -280px; margin-left: 18px; float: right; } ================================================ FILE: src/Frontend/Styles/font/specimen_files/specimen_stylesheet.css ================================================ @import url('grid_12-825-55-15.css'); /* CSS Reset by Eric Meyer - Released under Public Domain http://meyerweb.com/eric/tools/css/reset/ */ html, body, div, span, applet, object, iframe, h1, h2, h3, h4, h5, h6, p, blockquote, pre, a, abbr, acronym, address, big, cite, code, del, dfn, em, font, img, ins, kbd, q, s, samp, small, strike, strong, sub, sup, tt, var, b, u, i, center, dl, dt, dd, ol, ul, li, fieldset, form, label, legend, table, caption, tbody, tfoot, thead, tr, th, td { margin: 0; padding: 0; border: 0; outline: 0; font-size: 100%; vertical-align: baseline; background: transparent; } body { line-height: 1; } ol, ul { list-style: none; } blockquote, q { quotes: none; } blockquote:before, blockquote:after, q:before, q:after { content: ''; content: none; } :focus { outline: 0; } ins { text-decoration: none; } del { text-decoration: line-through; } table { border-collapse: collapse; border-spacing: 0; } body { color: #000; background-color: #dcdcdc; } a { text-decoration: none; color: #1883ba; } h1 { font-size: 32px; font-weight: normal; font-style: normal; margin-bottom: 18px; } h2 { font-size: 18px; } #container { width: 865px; margin: 0px auto; } #header { padding: 20px; font-size: 36px; background-color: #000; color: #fff; } #header span { color: #666; } #main_content { background-color: #fff; padding: 60px 20px 20px; } #footer p { margin: 0; padding-top: 10px; padding-bottom: 50px; color: #333; font: 10px Arial, sans-serif; } .tabs { width: 100%; height: 31px; background-color: #444; } .tabs li { float: left; margin: 0; overflow: hidden; background-color: #444; } .tabs li a { display: block; color: #fff; text-decoration: none; font: bold 11px/11px 'Arial'; text-transform: uppercase; padding: 10px 15px; border-right: 1px solid #fff; } .tabs li a:hover { background-color: #00b3ff; } .tabs li.active a { color: #000; background-color: #fff; } div.huge { font-size: 300px; line-height: 1em; padding: 0; letter-spacing: -0.02em; overflow: hidden; } div.glyph_range { font-size: 72px; line-height: 1.1em; } .size10 { font-size: 10px; } .size11 { font-size: 11px; } .size12 { font-size: 12px; } .size13 { font-size: 13px; } .size14 { font-size: 14px; } .size16 { font-size: 16px; } .size18 { font-size: 18px; } .size20 { font-size: 20px; } .size24 { font-size: 24px; } .size30 { font-size: 30px; } .size36 { font-size: 36px; } .size48 { font-size: 48px; } .size60 { font-size: 60px; } .size72 { font-size: 72px; } .size90 { font-size: 90px; } .psample_row1 { height: 120px; } .psample_row1 { height: 120px; } .psample_row2 { height: 160px; } .psample_row3 { height: 160px; } .psample_row4 { height: 160px; } .psample { overflow: hidden; position: relative; } .psample p { line-height: 1.3em; display: block; overflow: hidden; margin: 0; } .psample span { margin-right: 0.5em; } .white_blend { width: 100%; height: 61px; background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAVkAAAA9CAYAAAAH4BojAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAO1JREFUeNrs3TsKgFAMRUE/eer+NxztxMYuEWQG3ECKwwUF58ycAKixOAGAyAKILAAiCyCyACILgMgCiCyAyAIgsgAiCyCyAIgsgMgCiCwAIgsgsgAiC4DIAogsACIL0CWuZ3UGgLrIhjMA1EV2OAOAJQtgyQLwjOzmDAAiCyCyAIgsQFtkd2cAEFkAkQVAZAHaIns4A4AlC2DJAiCyACILILIAiCzAV5H1dQGAJQsgsgCILIDIAvwisl58AViyAJYsACILILIAIgvAe2T9EhxAZAFEFgCRBeiL7HAGgLrIhjMAWLIAliwAt1OAAQDwygTBulLIlQAAAABJRU5ErkJggg==); position: absolute; bottom: 0; } .black_blend { width: 100%; height: 61px; background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAVkAAAA9CAYAAAAH4BojAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAPJJREFUeNrs3TEKhTAQRVGjibr/9QoxhY2N3Ywo50A28IrLwP9g6b1PAMSYTQAgsgAiC4DIAogsgMgCILIAIgsgsgCILIDIAogsACILILIAIguAyAKILIDIAiCyACILgMgCZCnjLWYAiFGvB0BQZJsZAFyyAC5ZAO6RXc0AILIAIguAyAKkRXYzA4DIAogsACILkBbZ3QwALlkAlywAIgsgsgAiC4DIArwVWf8uAHDJAogsACILILIAv4isH74AXLIALlkARBZAZAFEFoDnyPokOIDIAogsACILkBfZZgaAuMhWMwC4ZAE+p4x3mAEgxinAAJ+XBbPWGkwAAAAAAElFTkSuQmCC); position: absolute; bottom: 0; } .fullreverse { background: #000 !important; color: #fff !important; margin-left: -20px; padding-left: 20px; margin-right: -20px; padding-right: 20px; padding: 20px; margin-bottom: 0; } .sample_table td { padding-top: 3px; padding-bottom: 5px; padding-left: 5px; vertical-align: middle; line-height: 1.2em; } .sample_table td:first-child { background-color: #eee; text-align: right; padding-right: 5px; padding-left: 0; padding: 5px; font: 11px/12px 'Courier New', Courier, mono; } code { white-space: pre; background-color: #eee; display: block; padding: 10px; margin-bottom: 18px; overflow: auto; } .bottom, .last { margin-bottom: 0 !important; padding-bottom: 0 !important; } .box { padding: 18px; margin-bottom: 18px; background: #eee; } .reverse, .reversed { background: #000 !important; color: #fff !important; border: none !important; } #bodycomparison { position: relative; overflow: hidden; font-size: 72px; height: 90px; white-space: nowrap; } #bodycomparison div { font-size: 72px; line-height: 90px; display: inline; margin: 0 15px 0 0; padding: 0; } #bodycomparison div span { font: 10px Arial; position: absolute; left: 0; } #xheight { float: none; position: absolute; color: #d9f3ff; font-size: 72px; line-height: 90px; } .fontbody { position: relative; } .arialbody { font-family: Arial; position: relative; } .verdanabody { font-family: Verdana; position: relative; } .georgiabody { font-family: Georgia; position: relative; } /* @group Layout page */ #layout h1 { font-size: 36px; line-height: 42px; font-weight: normal; font-style: normal; } #layout h2 { font-size: 24px; line-height: 23px; font-weight: normal; font-style: normal; } #layout h3 { font-size: 22px; line-height: 1.4em; margin-top: 1em; font-weight: normal; font-style: normal; } #layout p.byline { font-size: 12px; margin-top: 18px; line-height: 12px; margin-bottom: 0; } #layout p { font-size: 14px; line-height: 21px; margin-bottom: 0.5em; } #layout p.large { font-size: 18px; line-height: 26px; } #layout .sidebar p { font-size: 12px; line-height: 1.4em; } #layout p.caption { font-size: 10px; margin-top: -16px; margin-bottom: 18px; } /* @end */ /* @group Glyphs */ #glyph_chart div { background-color: #d9f3ff; color: black; float: left; font-size: 36px; height: 1.2em; line-height: 1.2em; margin-bottom: 1px; margin-right: 1px; text-align: center; width: 1.2em; position: relative; padding: 0.6em 0.2em 0.2em; } #glyph_chart div p { position: absolute; left: 0; top: 0; display: block; text-align: center; font: bold 9px Arial, sans-serif; background-color: #3a768f; width: 100%; color: #fff; padding: 2px 0; } #glyphs h1 { font-family: Arial, sans-serif; } /* @end */ /* @group Installing */ #installing { font: 13px Arial, sans-serif; } #installing p, #glyphs p { line-height: 1.2em; margin-bottom: 18px; font: 13px Arial, sans-serif; } #installing h3 { font-size: 15px; margin-top: 18px; } /* @end */ #rendering h1 { font-family: Arial, sans-serif; } .render_table td { font: 11px 'Courier New', Courier, mono; vertical-align: middle; } ================================================ FILE: src/Frontend/Styles/font/stylesheet.css ================================================ /*! Generated by Font Squirrel (https://www.fontsquirrel.com) on July 30, 2020 */ @font-face { font-family: 'perfect_dos_vga_437regular'; src: url('perfect_dos_vga_437-webfont.woff2') format('woff2'), url('perfect_dos_vga_437-webfont.woff') format('woff'); font-weight: normal; font-style: normal; } ================================================ FILE: src/Frontend/Styles/icomoon/style.css ================================================ @font-face { font-family: 'icomoon'; src: url('fonts/icomoon.eot'); src: url('fonts/icomoon.eot#iefix') format('embedded-opentype'), url('fonts/icomoon.ttf') format('truetype'), url('fonts/icomoon.woff') format('woff'), url('fonts/icomoon.svg#icomoon') format('svg'); font-weight: normal; font-style: normal; font-display: block; } [class^='icon-'], [class*=' icon-'] { /* use !important to prevent issues with browser extensions that change fonts */ font-family: 'icomoon' !important; speak: never; font-style: normal; font-weight: normal; font-variant: normal; text-transform: none; line-height: 1; /* Better Font Rendering =========== */ -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; } .icon-github:before { content: '\e903'; } .icon-discord:before { content: '\e900'; } .icon-twitter:before { content: '\e901'; } .icon-brand:before { content: '\e901'; } .icon-tweet:before { content: '\e901'; } .icon-social:before { content: '\e901'; } .icon-mail:before { content: '\e902'; } .icon-contact:before { content: '\e902'; } .icon-support:before { content: '\e902'; } .icon-newsletter:before { content: '\e902'; } .icon-letter:before { content: '\e902'; } .icon-email:before { content: '\e902'; } .icon-envelop:before { content: '\e902'; } .icon-social1:before { content: '\e902'; } ================================================ FILE: src/Frontend/Styles/preflight.css ================================================ /* Manually copied from https://unpkg.com/tailwindcss@1.2.0/dist/base.css. This helps us normalize CSS properties in an expected way that's friendly with web app development. */ /*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */ /* Document ========================================================================== */ /** * 1. Correct the line height in all browsers. * 2. Prevent adjustments of font size after orientation changes in iOS. */ html { line-height: 1.15; /* 1 */ -webkit-text-size-adjust: 100%; /* 2 */ } /* Sections ========================================================================== */ /** * Remove the margin in all browsers. */ body { margin: 0; } /** * Render the `main` element consistently in IE. */ main { display: block; } /** * Correct the font size and margin on `h1` elements within `section` and * `article` contexts in Chrome, Firefox, and Safari. */ h1 { font-size: 2em; margin: 0.67em 0; } /* Grouping content ========================================================================== */ /** * 1. Add the correct box sizing in Firefox. * 2. Show the overflow in Edge and IE. */ hr { box-sizing: content-box; /* 1 */ height: 0; /* 1 */ overflow: visible; /* 2 */ } /** * 1. Correct the inheritance and scaling of font size in all browsers. * 2. Correct the odd `em` font sizing in all browsers. */ pre { font-family: monospace, monospace; /* 1 */ font-size: 1em; /* 2 */ } /* Text-level semantics ========================================================================== */ /** * Remove the gray background on active links in IE 10. */ a { background-color: transparent; } /** * 1. Remove the bottom border in Chrome 57- * 2. Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari. */ abbr[title] { border-bottom: none; /* 1 */ text-decoration: underline; /* 2 */ -webkit-text-decoration: underline dotted; text-decoration: underline dotted; /* 2 */ } /** * Add the correct font weight in Chrome, Edge, and Safari. */ b, strong { font-weight: bolder; } /** * 1. Correct the inheritance and scaling of font size in all browsers. * 2. Correct the odd `em` font sizing in all browsers. */ code, kbd, samp { font-family: monospace, monospace; /* 1 */ font-size: 1em; /* 2 */ } /** * Add the correct font size in all browsers. */ small { font-size: 80%; } /** * Prevent `sub` and `sup` elements from affecting the line height in * all browsers. */ sub, sup { font-size: 75%; line-height: 0; position: relative; vertical-align: baseline; } sub { bottom: -0.25em; } sup { top: -0.5em; } /* Embedded content ========================================================================== */ /** * Remove the border on images inside links in IE 10. */ img { border-style: none; } /* Forms ========================================================================== */ /** * 1. Change the font styles in all browsers. * 2. Remove the margin in Firefox and Safari. */ button, input, optgroup, select, textarea { font-family: inherit; /* 1 */ font-size: 100%; /* 1 */ line-height: 1.15; /* 1 */ margin: 0; /* 2 */ } /** * Show the overflow in IE. * 1. Show the overflow in Edge. */ button, input { /* 1 */ overflow: visible; } /** * Remove the inheritance of text transform in Edge, Firefox, and IE. * 1. Remove the inheritance of text transform in Firefox. */ button, select { /* 1 */ text-transform: none; } /** * Correct the inability to style clickable types in iOS and Safari. */ button, [type='button'], [type='reset'], [type='submit'] { -webkit-appearance: button; } /** * Remove the inner border and padding in Firefox. */ button::-moz-focus-inner, [type='button']::-moz-focus-inner, [type='reset']::-moz-focus-inner, [type='submit']::-moz-focus-inner { border-style: none; padding: 0; } /** * Restore the focus styles unset by the previous rule. */ button:-moz-focusring, [type='button']:-moz-focusring, [type='reset']:-moz-focusring, [type='submit']:-moz-focusring { outline: 1px dotted ButtonText; } /** * Correct the padding in Firefox. */ fieldset { padding: 0.35em 0.75em 0.625em; } /** * 1. Correct the text wrapping in Edge and IE. * 2. Correct the color inheritance from `fieldset` elements in IE. * 3. Remove the padding so developers are not caught out when they zero out * `fieldset` elements in all browsers. */ legend { box-sizing: border-box; /* 1 */ color: inherit; /* 2 */ display: table; /* 1 */ max-width: 100%; /* 1 */ padding: 0; /* 3 */ white-space: normal; /* 1 */ } /** * Add the correct vertical alignment in Chrome, Firefox, and Opera. */ progress { vertical-align: baseline; } /** * Remove the default vertical scrollbar in IE 10+. */ textarea { overflow: auto; } /** * 1. Add the correct box sizing in IE 10. * 2. Remove the padding in IE 10. */ [type='checkbox'], [type='radio'] { box-sizing: border-box; /* 1 */ padding: 0; /* 2 */ } /** * Correct the cursor style of increment and decrement buttons in Chrome. */ [type='number']::-webkit-inner-spin-button, [type='number']::-webkit-outer-spin-button { height: auto; } /** * 1. Correct the odd appearance in Chrome and Safari. * 2. Correct the outline style in Safari. */ [type='search'] { -webkit-appearance: textfield; /* 1 */ outline-offset: -2px; /* 2 */ } /** * Remove the inner padding in Chrome and Safari on macOS. */ [type='search']::-webkit-search-decoration { -webkit-appearance: none; } /** * 1. Correct the inability to style clickable types in iOS and Safari. * 2. Change font properties to `inherit` in Safari. */ ::-webkit-file-upload-button { -webkit-appearance: button; /* 1 */ font: inherit; /* 2 */ } /* Interactive ========================================================================== */ /* * Add the correct display in Edge, IE 10+, and Firefox. */ details { display: block; } /* * Add the correct display in all browsers. */ summary { display: list-item; } /* Misc ========================================================================== */ /** * Add the correct display in IE 10+. */ template { display: none; } /** * Add the correct display in IE 10. */ [hidden] { display: none; } /** * Manually forked from SUIT CSS Base: https://github.com/suitcss/base * A thin layer on top of normalize.css that provides a starting point more * suitable for web applications. */ /** * Removes the default spacing and border for appropriate elements. */ blockquote, dl, dd, h1, h2, h3, h4, h5, h6, hr, figure, p, pre { margin: 0; } button { background-color: transparent; background-image: none; padding: 0; } /** * Work around a Firefox/IE bug where the transparent `button` background * results in a loss of the default `button` focus styles. */ button:focus { outline: 1px dotted; outline: 5px auto -webkit-focus-ring-color; } fieldset { margin: 0; padding: 0; } ol, ul { list-style: none; margin: 0; padding: 0; } /** * Tailwind custom reset styles */ /** * 1. Use the user's configured `sans` font-family (with Tailwind's default * sans-serif font stack as a fallback) as a sane default. * 2. Use Tailwind's default "normal" line-height so the user isn't forced * to override it to ensure consistency even when using the default theme. */ html { font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, 'Noto Sans', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'; /* 1 */ line-height: 1.5; /* 2 */ } /** * 1. Prevent padding and border from affecting element width. * * We used to set this in the html element and inherit from * the parent element for everything else. This caused issues * in shadow-dom-enhanced elements like
where the content * is wrapped by a div with box-sizing set to `content-box`. * * https://github.com/mozdevs/cssremedy/issues/4 * * * 2. Allow adding a border to an element by just adding a border-width. * * By default, the way the browser specifies that an element should have no * border is by setting it's border-style to `none` in the user-agent * stylesheet. * * In order to easily add borders to elements by just setting the `border-width` * property, we change the default border-style for all elements to `solid`, and * use border-width to hide them instead. This way our `border` utilities only * need to set the `border-width` property instead of the entire `border` * shorthand, making our border utilities much more straightforward to compose. * * https://github.com/tailwindcss/tailwindcss/pull/116 */ *, ::before, ::after { box-sizing: border-box; /* 1 */ border-width: 0; /* 2 */ border-style: solid; /* 2 */ border-color: #e2e8f0; /* 2 */ } /* * Ensure horizontal rules are visible by default */ hr { border-top-width: 1px; } /** * Undo the `border-style: none` reset that Normalize applies to images so that * our `border-{width}` utilities have the expected effect. * * The Normalize reset is unnecessary for us since we default the border-width * to 0 on all elements. * * https://github.com/tailwindcss/tailwindcss/issues/362 */ img { border-style: solid; } textarea { resize: vertical; } input:-ms-input-placeholder, textarea:-ms-input-placeholder { color: #a0aec0; } input::-ms-input-placeholder, textarea::-ms-input-placeholder { color: #a0aec0; } input::placeholder, textarea::placeholder { color: #a0aec0; } button, [role='button'] { cursor: pointer; } table { border-collapse: collapse; } h1, h2, h3, h4, h5, h6 { font-size: inherit; font-weight: inherit; } /** * Reset links to optimize for opt-in styling instead of * opt-out. */ a { color: inherit; text-decoration: inherit; } /** * Reset form element properties that are easy to forget to * style explicitly so you don't inadvertently introduce * styles that deviate from your design system. These styles * supplement a partial reset that is already applied by * normalize.css. */ button, input, optgroup, select, textarea { padding: 0; line-height: inherit; color: inherit; } /** * Use the configured 'mono' font family for elements that * are expected to be rendered with a monospace font, falling * back to the system monospace stack if there is no configured * 'mono' font family. */ pre, code, kbd, samp { font-family: Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace; } /** * Make replaced elements `display: block` by default as that's * the behavior you want almost all of the time. Inspired by * CSS Remedy, with `svg` added as well. * * https://github.com/mozdevs/cssremedy/issues/14 */ img, svg, video, canvas, audio, iframe, embed, object { display: block; vertical-align: middle; } /** * Constrain images and videos to the parent width and preserve * their instrinsic aspect ratio. * * https://github.com/mozdevs/cssremedy/issues/14 */ img, video { max-width: 100%; height: auto; } /*# sourceMappingURL=base.css.map */ ================================================ FILE: src/Frontend/Styles/style.css ================================================ @import url('https://fonts.googleapis.com/css2?family=Inconsolata:wght@300&display=swap'); html, body, #root { height: 100%; } ::-webkit-scrollbar { width: 0px; background: transparent; } body { font-family: 'Inconsolata', monospace; font-weight: 300; } /* Hide scrollbar for Chrome, Safari and Opera */ *::-webkit-scrollbar { display: none; } /* Hide scrollbar for IE, Edge and Firefox */ * { -ms-overflow-style: none; /* IE and Edge */ scrollbar-width: none; /* Firefox */ } ================================================ FILE: src/Frontend/Utils/AppHooks.ts ================================================ import { getActivatedArtifact, isActivated } from '@darkforest_eth/gamelogic'; import { Artifact, ArtifactId, EthAddress, Leaderboard, LocationId, Planet, Player, Transaction, TransactionId, } from '@darkforest_eth/types'; import { useCallback, useEffect, useMemo, useState } from 'react'; import GameUIManager from '../../Backend/GameLogic/GameUIManager'; import { loadLeaderboard } from '../../Backend/Network/LeaderboardApi'; import { Wrapper } from '../../Backend/Utils/Wrapper'; import { ContractsAPIEvent } from '../../_types/darkforest/api/ContractsAPITypes'; import { ModalHandle } from '../Views/ModalPane'; import { createDefinedContext } from './createDefinedContext'; import { useEmitterSubscribe, useEmitterValue, useWrappedEmitter } from './EmitterHooks'; import { usePoll } from './Hooks'; export const { useDefinedContext: useUIManager, provider: UIManagerProvider } = createDefinedContext(); export const { useDefinedContext: useTopLevelDiv, provider: TopLevelDivProvider } = createDefinedContext(); export function useOverlayContainer(): HTMLDivElement | null { return useUIManager()?.getOverlayContainer() ?? null; } /** * Get the currently used account on the client. * @param uiManager instance of GameUIManager */ export function useAccount(uiManager: GameUIManager): EthAddress | undefined { const account = useMemo(() => uiManager.getAccount(), [uiManager]); return account; } /** * Hook which gets you the player, and updates whenever that player's twitter or score changes. */ export function usePlayer( uiManager: GameUIManager, ethAddress?: EthAddress ): Wrapper { const [player, setPlayer] = useState>( () => new Wrapper(uiManager.getPlayer(ethAddress)) ); useEmitterSubscribe( uiManager.getGameManager().playersUpdated$, () => { setPlayer(new Wrapper(uiManager.getPlayer(ethAddress))); }, [uiManager, setPlayer, ethAddress] ); return player; } /** * Create a subscription to the currently selected planet. * @param uiManager instance of GameUIManager */ export function useSelectedPlanet(uiManager: GameUIManager): Wrapper { const selectedPlanetId = useWrappedEmitter(uiManager.selectedPlanetId$, undefined); return usePlanet(uiManager, selectedPlanetId.value); } export function useSelectedPlanetId(uiManager: GameUIManager, defaultId?: LocationId) { return useWrappedEmitter(uiManager.selectedPlanetId$, defaultId); } export function usePlanet( uiManager: GameUIManager, locationId: LocationId | undefined ): Wrapper { const [planet, setPlanet] = useState>( () => new Wrapper(uiManager.getPlanetWithId(locationId)) ); useEffect(() => { setPlanet(new Wrapper(uiManager.getPlanetWithId(locationId))); }, [uiManager, locationId]); useEmitterSubscribe( uiManager.getGameManager().getGameObjects().planetUpdated$, (id: LocationId) => { if (id === locationId) { setPlanet(new Wrapper(uiManager.getPlanetWithId(locationId))); } }, [uiManager, setPlanet, locationId] ); return planet; } /** * Create a subscription to the currently hovering planet. * @param uiManager instance of GameUIManager */ export function useHoverPlanet(uiManager: GameUIManager): Wrapper { return useWrappedEmitter(uiManager.hoverPlanet$, undefined); } export function useHoverArtifact(uiManager: GameUIManager): Wrapper { return useWrappedEmitter(uiManager.hoverArtifact$, undefined); } export function useHoverArtifactId(uiManager: GameUIManager): Wrapper { return useWrappedEmitter(uiManager.hoverArtifactId$, undefined); } export function useMyArtifactsList(uiManager: GameUIManager) { const [myArtifacts, setMyArtifacts] = useState(uiManager.getMyArtifacts()); useEmitterSubscribe( uiManager.getArtifactUpdated$(), () => { setMyArtifacts(uiManager.getMyArtifacts()); }, [uiManager, setMyArtifacts] ); return myArtifacts; } // note that this is going to throw an error if the pointer to `artifacts` changes but not to `planet` export function usePlanetArtifacts( planet: Wrapper, uiManager: GameUIManager ): Artifact[] { const artifacts = useMemo( () => (planet.value ? uiManager.getArtifactsWithIds(planet.value.heldArtifactIds) : []), [planet, uiManager] ); return artifacts.filter((a) => !!a) as Artifact[]; } export function usePlanetInactiveArtifacts( planet: Wrapper, uiManager: GameUIManager ): Artifact[] { const artifacts = usePlanetArtifacts(planet, uiManager); const filtered = useMemo(() => artifacts.filter((a) => !isActivated(a)), [artifacts]); return filtered; } export function useActiveArtifact( planet: Wrapper, uiManager: GameUIManager ): Artifact | undefined { const artifacts = usePlanetArtifacts(planet, uiManager); return getActivatedArtifact(artifacts); } /** * Create a subscription to the currently selected artifact. * @param uiManager instance of GameUIManager */ export function useSelectedArtifact(uiManager: GameUIManager): Wrapper { return useWrappedEmitter(uiManager.hoverArtifact$, undefined); } export function useArtifact(uiManager: GameUIManager, artifactId: ArtifactId) { const [artifact, setArtifact] = useState>( new Wrapper(uiManager.getArtifactWithId(artifactId)) ); useEmitterSubscribe( uiManager.getGameManager().getGameObjects().artifactUpdated$, (id: ArtifactId) => { if (id === artifactId) { setArtifact(new Wrapper(uiManager.getArtifactWithId(artifactId))); } }, [uiManager, setArtifact, artifactId] ); useEffect(() => { setArtifact(new Wrapper(uiManager.getArtifactWithId(artifactId))); }, [uiManager, artifactId]); return artifact; } // TODO cache this globally /** Loads the leaderboard */ export function useLeaderboard(poll: number | undefined = undefined): { leaderboard: Leaderboard | undefined; error: Error | undefined; } { const [leaderboard, setLeaderboard] = useState(); const [error, setError] = useState(); const load = useCallback(async function load() { try { setLeaderboard(await loadLeaderboard()); } catch (e) { console.log('error loading leaderboard', e); setError(e); } }, []); usePoll(load, poll, true); return { leaderboard, error }; } export function usePopAllOnSelectedPlanetChanged( modal: ModalHandle, startingId: LocationId | undefined ) { const selected = useSelectedPlanetId(useUIManager(), startingId).value; useEffect(() => { if (selected !== startingId) { modal.popAll(); } }, [selected, modal, startingId]); } export type TransactionRecord = Record; /** * Creates subscriptions to all contract transaction events to keep an up to date * list of all transactions and their states. */ export function useTransactionLog() { const uiManager = useUIManager(); const [transactions, setTransactions] = useState>(new Wrapper({})); /** * Update the matching transaction in the {@link TransactionRecord} * with data from the contract lifecycle events. A {@link Wrapper} * around the {@link TransactionRecord} needs to be used to avoid * force a React state change and re-render. */ const updateTransaction = useCallback((tx: Transaction) => { setTransactions((txs) => { const txWrapper = new Wrapper(txs.value); txWrapper.value[tx.id] = { ...tx, }; return txWrapper; }); }, []); useEffect(() => { const gameManager = uiManager.getGameManager(); const contractEventEmitter = gameManager.getContractAPI(); contractEventEmitter.on(ContractsAPIEvent.TxQueued, updateTransaction); contractEventEmitter.on(ContractsAPIEvent.TxPrioritized, updateTransaction); contractEventEmitter.on(ContractsAPIEvent.TxProcessing, updateTransaction); contractEventEmitter.on(ContractsAPIEvent.TxSubmitted, updateTransaction); contractEventEmitter.on(ContractsAPIEvent.TxConfirmed, updateTransaction); contractEventEmitter.on(ContractsAPIEvent.TxErrored, updateTransaction); contractEventEmitter.on(ContractsAPIEvent.TxCancelled, updateTransaction); return () => { contractEventEmitter.off(ContractsAPIEvent.TxQueued, updateTransaction); contractEventEmitter.off(ContractsAPIEvent.TxPrioritized, updateTransaction); contractEventEmitter.off(ContractsAPIEvent.TxProcessing, updateTransaction); contractEventEmitter.off(ContractsAPIEvent.TxSubmitted, updateTransaction); contractEventEmitter.off(ContractsAPIEvent.TxConfirmed, updateTransaction); contractEventEmitter.off(ContractsAPIEvent.TxErrored, updateTransaction); contractEventEmitter.off(ContractsAPIEvent.TxCancelled, updateTransaction); }; }, [uiManager, updateTransaction]); return transactions; } export function usePaused() { const ui = useUIManager(); return useEmitterValue(ui.getPaused$(), ui.getPaused()); } ================================================ FILE: src/Frontend/Utils/BrowserChecks.ts ================================================ import _ from 'lodash'; export const enum Incompatibility { NoIDB = 'no_idb', NotRopsten = 'not_ropsten', MobileOrTablet = 'mobile_or_tablet', UnsupportedBrowser = 'unsupported_browser', NotLoggedInOrEnabled = 'not_logged_in_or_enabled', UnexpectedError = 'unexpected_error', } export const hasTouchscreen = () => { // @ts-ignore TS2551: Property 'msMaxTouchPoints' does not exist on type 'Navigator' return 'ontouchstart' in window || navigator.maxTouchPoints > 0 || navigator.msMaxTouchPoints > 0; }; const supportsIDB = () => { return 'indexedDB' in window; }; // modified, original from https://stackoverflow.com/questions/11381673/detecting-a-mobile-browser export const isMobileOrTablet = () => { let check = false; (function (a) { if ( /(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows ce|xda|xiino|android|ipad|playbook|silk/i.test( a as string ) || /1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i.test( (a as string).substr(0, 4) ) ) check = true; })(navigator.userAgent || navigator.vendor || 'opera' in window); return check; }; const isSupportedBrowser = async () => isChrome() || isFirefox() || (await isBrave()); type FeatureList = Partial>; const checkFeatures = async (): Promise => { const incompats: FeatureList = {}; try { incompats[Incompatibility.UnsupportedBrowser] = !(await isSupportedBrowser()); incompats[Incompatibility.NoIDB] = !supportsIDB(); incompats[Incompatibility.MobileOrTablet] = isMobileOrTablet(); } catch (e) { console.error(e); incompats[Incompatibility.UnexpectedError] = true; } return incompats; }; export const unsupportedFeatures = async (): Promise => { const features = await checkFeatures(); return _.keys(features).filter((f: Incompatibility) => features[f]) as Incompatibility[]; }; export const isFirefox = () => navigator.userAgent.indexOf('Firefox') > 0; export const isChrome = () => /Google Inc/.test(navigator.vendor); export const isBrave = async () => !!((navigator as any).brave && (await (navigator as any).brave.isBrave())); // eslint-disable-line @typescript-eslint/no-explicit-any ================================================ FILE: src/Frontend/Utils/EmitterHooks.ts ================================================ import { Callback, Monomitter } from '@darkforest_eth/events'; import { useEffect, useState } from 'react'; import { Wrapper } from '../../Backend/Utils/Wrapper'; /** * Execute something on emitter callback * @param emitter `Monomitter` to subscribe to * @param callback callback to subscribe */ export function useEmitterSubscribe( emitter: Monomitter, callback: Callback, deps: React.DependencyList ) { useEffect( () => { return emitter.subscribe(callback).unsubscribe; }, // Disable exhaustive because we don't change if the callback changes, only deps // eslint-disable-next-line react-hooks/exhaustive-deps [ emitter, // eslint-disable-next-line react-hooks/exhaustive-deps ...deps, ] ); } /** * Use returned value from an emitter * @param emitter `Monomitter` to subscribe to * @param initialVal initial state value */ export function useEmitterValue(emitter: Monomitter, initialVal: T) { const [val, setVal] = useState(initialVal); useEffect(() => { const sub = emitter.subscribe((v) => setVal(v)); return sub.unsubscribe; }, [emitter]); return val; } /** * Use returned value from an emitter, and clone the reference - used to force an update to the UI * @param emitter `Monomitter` to subscribe to * @param initialVal initial state value */ export function useWrappedEmitter( emitter: Monomitter, initialVal: T | undefined ): Wrapper { const [val, setVal] = useState>(new Wrapper(initialVal)); useEffect(() => { const sub = emitter.subscribe((v) => { setVal(new Wrapper(v)); }); return sub.unsubscribe; }, [emitter]); return val; } ================================================ FILE: src/Frontend/Utils/EmitterUtils.ts ================================================ import { monomitter, Monomitter } from '@darkforest_eth/events'; import { isSpaceShip } from '@darkforest_eth/gamelogic'; import { Artifact, EthAddress, Planet } from '@darkforest_eth/types'; import _ from 'lodash'; /** * Create a monomitter to emit objects with a given id from a cached map of ids to objects. * @param objMap the cached map of `` * @param objId$ the object id to select * @param objUpdated$ emitter which indicates when an object has been updated */ export function getObjectWithIdFromMap( objMap: Map, objId$: Monomitter, objUpdated$: Monomitter ): Monomitter { let lastId: Id | undefined; const selectedObj$ = monomitter(); const publishIdEvent = (id: Id | undefined) => { // emit object associated with id selectedObj$.publish(id ? objMap.get(id) : undefined); }; objId$.subscribe((id: Id | undefined) => { // Publish event for new object publishIdEvent(id); //Update tracked object lastId = id; }); objUpdated$.subscribe((id: Id) => { if (lastId && lastId === id) publishIdEvent(id); }); return selectedObj$; } /** * Create a monomitter to emit objects with a given id from a cached map of ids to objects. Not intended for re-use * @param objMap the cached map of `` * @param objId the object id to select * @param objUpdated$ emitter which indicates when an object has been updated */ export function getDisposableEmitter( objMap: Map, objId: Id, objUpdated$: Monomitter ): Monomitter { const selectedObj$ = monomitter(); const publishIdEvent = (id: Id | undefined) => { // emit value of new id selectedObj$.publish(id ? objMap.get(id) : undefined); }; objUpdated$.subscribe((id: Id) => { if (objId === id) publishIdEvent(id); }); return selectedObj$; } /** * Utility function for setting a game entity into our internal data stores in a way * that is friendly to our application. Caches the object into a map, syncs it to a map * of our owned objects, and also emits a message that the object was updated. * @param objectMap map that caches known objects * @param myObjectMap map that caches known objects owned by the user * @param address the user's account address * @param obj the object we want to cache * @param objUpdated$ emitter for announcing object updates */ export function setObjectSyncState( objectMap: Map, myObjectMap: Map, address: EthAddress | undefined, objUpdated$: Monomitter, myObjListUpdated$: Monomitter>, getId: (o: Obj) => Id, getOwner: (o: Obj) => EthAddress, obj: Obj ): void { objectMap.set(getId(obj), obj); let myObjListChanged = false; if (address === getOwner(obj)) { myObjectMap.set(getId(obj), obj); myObjListChanged = true; } else if (myObjectMap.has(getId(obj))) { myObjectMap.delete(getId(obj)); myObjListChanged = true; } objUpdated$.publish(getId(obj)); if (myObjListChanged) { myObjListUpdated$.publish(myObjectMap); } } /** * @param previous The previously emitted state of an object * @param current The current emitted state of an object */ export interface Diff { previous: Type; current: Type; } /** * Wraps an existing emitter and emits an event with the current and previous values * @param emitter an emitter announcing game objects */ export function generateDiffEmitter( emitter: Monomitter ): Monomitter | undefined> { let prevInstance: Obj | undefined; const currPrevEmitter$ = monomitter | undefined>(); emitter.subscribe((instance) => { currPrevEmitter$.publish({ current: _.cloneDeep(instance) as Obj, previous: prevInstance ? _.cloneDeep(prevInstance) : (_.cloneDeep(instance) as Obj), }); prevInstance = _.cloneDeep(instance); }); return currPrevEmitter$; } /* i'm slightly worried that function literals would have to get allocated and de-allocated all the time - the below functions exist in order to prevent that */ export const getPlanetId = (p: Planet) => p.locationId; export const getPlanetOwner = (p: Planet) => p.owner; export const getArtifactId = (a: Artifact) => a.id; export const getArtifactOwner = (a: Artifact) => { if (isSpaceShip(a.artifactType)) { return a.controller; } return a.currentOwner; }; ================================================ FILE: src/Frontend/Utils/Hooks.tsx ================================================ import { useEffect } from 'react'; /** * Executes the callback `cb` every `poll` ms * @param cb callback to execute * @param poll ms to poll * @param execFirst if we want to execute the callback on first render */ export function usePoll( cb: () => void, poll: number | undefined = undefined, execFirst: boolean | undefined = undefined ) { useEffect(() => { if (execFirst) cb(); if (!poll) return; const interval = setInterval(cb, poll); return () => clearInterval(interval); }, [poll, cb, execFirst]); } ================================================ FILE: src/Frontend/Utils/KeyEmitters.ts ================================================ import { SpecialKey } from '@darkforest_eth/constants'; import { monomitter } from '@darkforest_eth/events'; import { Setting } from '@darkforest_eth/types'; import { useEffect, useState } from 'react'; import { Wrapper } from '../../Backend/Utils/Wrapper'; import { useUIManager } from './AppHooks'; import { useEmitterSubscribe } from './EmitterHooks'; import { useBooleanSetting } from './SettingsHooks'; export const keyUp$ = monomitter>(); export const keyDown$ = monomitter>(); const onKeyUp = (e: KeyboardEvent) => { if (!shouldIgnoreShortcutKeypress(e)) keyUp$.publish(new Wrapper(getSpecialKeyFromEvent(e) || e.key.toLowerCase())); }; const onKeyDown = (e: KeyboardEvent) => { if (!shouldIgnoreShortcutKeypress(e)) keyDown$.publish(new Wrapper(getSpecialKeyFromEvent(e) || e.key.toLowerCase())); }; export function listenForKeyboardEvents() { document.addEventListener('keydown', onKeyDown); document.addEventListener('keyup', onKeyUp); } export function unlinkKeyboardEvents() { document.removeEventListener('keydown', onKeyDown); document.removeEventListener('keyup', onKeyUp); } /** * If the user is using their keyboard to input some text somewhere, we should NOT trigger the * shortcuts. */ function shouldIgnoreShortcutKeypress(e: KeyboardEvent): boolean { const targetElement = e.target as HTMLElement; if (targetElement.tagName === 'INPUT') return targetElement.attributes.getNamedItem('type')?.value !== 'range'; return targetElement.tagName === 'TEXTAREA'; } function getSpecialKeyFromEvent(e: KeyboardEvent): string | undefined { if ((Object.values(SpecialKey) as string[]).includes(e.key)) { return e.key; } } export function useIsDown(key?: string) { const [isDown, setIsDown] = useState(false); useEmitterSubscribe(keyDown$, (k) => key !== undefined && k.value === key && setIsDown(true), [ key, setIsDown, ]); useEmitterSubscribe(keyUp$, (k) => key !== undefined && k.value === key && setIsDown(false), [ key, setIsDown, ]); return isDown; } export function useOnUp(key: string, onUp: () => void, deps: React.DependencyList = []) { const [disableDefaultShortcuts] = useBooleanSetting( useUIManager(), Setting.DisableDefaultShortcuts ); useEffect(() => { const onKeyUp = (e: KeyboardEvent) => { if ( (getSpecialKeyFromEvent(e) === key || e.key.toLowerCase() === key) && !shouldIgnoreShortcutKeypress(e) ) { !disableDefaultShortcuts && onUp && onUp(); } }; document.addEventListener('keyup', onKeyUp); return () => document.removeEventListener('keyup', onKeyUp); }, [ key, onUp, disableDefaultShortcuts, // eslint-disable-next-line react-hooks/exhaustive-deps ...deps, ]); } ================================================ FILE: src/Frontend/Utils/SettingsHooks.tsx ================================================ import { monomitter, Monomitter } from '@darkforest_eth/events'; import { AutoGasSetting, EthAddress, Setting } from '@darkforest_eth/types'; import React, { useCallback, useState } from 'react'; import GameUIManager from '../../Backend/GameLogic/GameUIManager'; import { SelectFrom } from '../Components/CoreUI'; import { Checkbox, ColorInput, DarkForestCheckbox, DarkForestColorInput, DarkForestNumberInput, DarkForestTextInput, NumberInput, TextInput, } from '../Components/Input'; import { useEmitterSubscribe } from './EmitterHooks'; /** * Whenever a setting changes, we publish the setting's name to this event emitter. */ export const settingChanged$: Monomitter = monomitter(); export const ALL_AUTO_GAS_SETTINGS = [ AutoGasSetting.Slow, AutoGasSetting.Average, AutoGasSetting.Fast, ]; function onlyInProduction(): string { return process.env.NODE_ENV === 'production' ? 'true' : 'false'; } function onlyInDevelopment(): string { return process.env.NODE_ENV !== 'production' ? 'true' : 'false'; } const defaultSettings: Record = { [Setting.OptOutMetrics]: onlyInDevelopment(), [Setting.AutoApproveNonPurchaseTransactions]: onlyInDevelopment(), [Setting.DrawChunkBorders]: 'false', [Setting.HighPerformanceRendering]: 'false', [Setting.MoveNotifications]: 'true', [Setting.HasAcceptedPluginRisk]: onlyInDevelopment(), [Setting.GasFeeGwei]: AutoGasSetting.Average, [Setting.TerminalVisible]: 'true', [Setting.TutorialOpen]: onlyInProduction(), [Setting.FoundPirates]: 'false', [Setting.TutorialCompleted]: 'false', [Setting.FoundSilver]: 'false', [Setting.FoundSilverBank]: 'false', [Setting.FoundTradingPost]: 'false', [Setting.FoundComet]: 'false', [Setting.FoundArtifact]: 'false', [Setting.FoundDeepSpace]: 'false', [Setting.FoundSpace]: 'false', // prevent the tutorial and help pane popping up in development mode. [Setting.NewPlayer]: onlyInProduction(), [Setting.MiningCores]: '1', [Setting.IsMining]: 'true', [Setting.DisableDefaultShortcuts]: 'false', [Setting.ExperimentalFeatures]: 'false', [Setting.DisableEmojiRendering]: 'false', [Setting.DisableHatRendering]: 'false', [Setting.AutoClearConfirmedTransactionsAfterSeconds]: '-1', [Setting.AutoClearRejectedTransactionsAfterSeconds]: '-1', [Setting.DisableFancySpaceEffect]: 'false', [Setting.RendererColorInnerNebula]: '#186469', [Setting.RendererColorNebula]: '#0B2B5B', [Setting.RendererColorSpace]: '#0B0F34', [Setting.RendererColorDeepSpace]: '#0B061F', [Setting.RendererColorDeadSpace]: '#11291b', [Setting.ForceReloadEmbeddedPlugins]: 'false', }; interface SettingStorageConfig { contractAddress: EthAddress; account: EthAddress | undefined; } /** * Each setting is stored in local storage. Each account has their own setting. */ export function getLocalStorageSettingKey( { contractAddress, account }: SettingStorageConfig, setting: Setting ): string { if (account === undefined) { return contractAddress + ':anonymous:' + setting; } return contractAddress + ':' + account + ':' + setting; } /** * Read the local storage setting from local storage. */ export function getSetting(config: SettingStorageConfig, setting: Setting): string { const key = getLocalStorageSettingKey(config, setting); let valueInStorage = localStorage.getItem(key); if (valueInStorage === null) { valueInStorage = defaultSettings[setting]; } return valueInStorage; } /** * Save the given setting to local storage. Publish an event to {@link settingChanged$}. */ export function setSetting( { contractAddress, account }: SettingStorageConfig, setting: Setting, value: string ): void { const keyInLocalStorage = account && getLocalStorageSettingKey({ contractAddress, account }, setting); if (keyInLocalStorage === undefined || account === undefined) { return; } localStorage.setItem(keyInLocalStorage, value); settingChanged$.publish(setting); } /** * Loads from local storage, and interprets as a boolean the setting with the given name. */ export function getBooleanSetting(config: SettingStorageConfig, setting: Setting): boolean { const value = getSetting(config, setting); return value === 'true'; } /** * Save the given setting to local storage. Publish an event to {@link settingChanged$}. */ export function setBooleanSetting(config: SettingStorageConfig, setting: Setting, value: boolean) { setSetting(config, setting, value + ''); } /** * Loads from local storage, and interprets as a boolean the setting with the given name. */ export function getNumberSetting(config: SettingStorageConfig, setting: Setting): number { const value = getSetting(config, setting); const parsedValue = parseFloat(value); if (isNaN(parsedValue)) { return parseFloat(defaultSettings[setting]); } return parsedValue; } /** * Save the given setting to local storage. Publish an event to {@link settingChanged$}. */ export function setNumberSetting(config: SettingStorageConfig, setting: Setting, value: number) { setSetting(config, setting, value + ''); } /** * Allows a react component to subscribe to changes and set the given setting. */ export function useSetting( uiManager: GameUIManager, setting: Setting ): [string, (newValue: string | undefined) => void] { const contractAddress = uiManager.getContractAddress(); const account = uiManager.getAccount(); const config = { contractAddress, account }; const [settingValue, setSettingValue] = useState(() => getSetting(config, setting)); useEmitterSubscribe( settingChanged$, (changedSetting: Setting) => { if (changedSetting === setting) { setSettingValue(getSetting(config, changedSetting)); } }, [setting, setSettingValue, getSetting] ); return [ settingValue, (newValue: string) => { setSetting(config, setting, newValue); }, ]; } export function StringSetting({ uiManager, setting, settingDescription, }: { uiManager: GameUIManager; setting: Setting; settingDescription?: string; }) { const [settingValue, setSettingValue] = useSetting(uiManager, setting); const onChange = useCallback( (e: Event & React.ChangeEvent) => { setSettingValue(e.target.value); }, [setSettingValue] ); return ( <> {settingDescription}
); } export function ColorSetting({ uiManager, setting, settingDescription, }: { uiManager: GameUIManager; setting: Setting; settingDescription?: string; }) { const [settingValue, setSettingValue] = useSetting(uiManager, setting); const onChange = useCallback( (e: Event & React.ChangeEvent) => { setSettingValue(e.target.value); }, [setSettingValue] ); return ( <> {settingDescription}
); } /** * Allows a react component to subscribe to changes and set the given setting as a number. Doesn't * allow you to set the value of this setting to anything but a valid number. */ export function useNumberSetting( uiManager: GameUIManager, setting: Setting ): [number, (newValue: number) => void] { const [stringSetting, setStringSetting] = useSetting(uiManager, setting); let parsedNumber = parseInt(stringSetting, 10); if (isNaN(parsedNumber)) { parsedNumber = 0; } return [ parsedNumber, (newValue: number) => { setStringSetting(newValue + ''); }, ]; } /** * Allows a react component to subscribe to changes to the given setting, interpreting its value as * a boolean. */ export function useBooleanSetting( uiManager: GameUIManager, setting: Setting ): [boolean, (newValue: boolean) => void] { const [stringSetting, setStringSetting] = useSetting(uiManager, setting); const booleanValue = stringSetting === 'true'; return [ booleanValue, (newValue: boolean) => { setStringSetting(newValue + ''); }, ]; } /** * React component that renders a checkbox representing the current value of this particular * setting, interpreting its value as a boolean. Allows the player to click on the checkbox to * toggle the setting. Toggling the setting both notifies the rest of the game that the given * setting was changed, and also saves it to local storage. */ export function BooleanSetting({ uiManager, setting, settingDescription, }: { uiManager: GameUIManager; setting: Setting; settingDescription?: string; }) { const [settingValue, setSettingValue] = useBooleanSetting(uiManager, setting); return ( ) => setSettingValue(e.target.checked) } /> ); } export function NumberSetting({ uiManager, setting, }: { uiManager: GameUIManager; setting: Setting; }) { const [settingValue, setSettingValue] = useNumberSetting(uiManager, setting); return ( ) => { if (e.target.value) { setSettingValue(e.target.value); } }} /> ); } /** * UI that is kept in-sync with a particular setting which allows you to set that setting to one of * several options. */ export function MultiSelectSetting({ uiManager, setting, values, labels, style, wide, }: { uiManager: GameUIManager; setting: Setting; values: string[]; labels: string[]; style?: React.CSSProperties; wide?: boolean; }) { const [settingValue, setSettingValue] = useSetting(uiManager, setting); return ( ); } /** * Some settings can be set from another browser window. In particular, the 'auto accept * transaction' setting is set from multiple browser windows. As a result, the local storage setting * can get out of sync with the in memory setting. To fix this, we can poll the given setting from * local storage, and notify the rest of the game that it changed if it changed. */ export function pollSetting( config: SettingStorageConfig, setting: Setting ): ReturnType { const SETTING_POLL_INTERVAL = 1000; const value = getSetting(config, setting); return setInterval(() => { const newValue = getSetting(config, setting); if (value !== newValue) { settingChanged$.publish(setting); } }, SETTING_POLL_INTERVAL); } ================================================ FILE: src/Frontend/Utils/ShortcutConstants.ts ================================================ import { SpecialKey } from '@darkforest_eth/constants'; // modal shortcuts export const MODAL_BACK_SHORTCUT = 't'; export const CLOSE_MODAL = 't'; export const TOGGLE_SETTINGS_PANE = 'h'; export const TOGGLE_HELP_PANE = 'j'; export const TOGGLE_PLUGINS_PANE = 'k'; export const TOGGLE_YOUR_ARTIFACTS_PANE = 'l'; export const TOGGLE_YOUR_PLANETS_DEX_PANE = ';'; export const TOGGLE_TRANSACTIONS_PANE = "'"; // planet context pane shortcuts export const TOGGLE_PLANET_ARTIFACTS_PANE = 's'; export const TOGGLE_HAT_PANE = 'x'; export const TOGGLE_ABANDON = 'r'; export const INVADE = 'y'; export const MINE_ARTIFACT = 'f'; export const TOGGLE_BROADCAST_PANE = 'z'; export const TOGGLE_UPGRADES_PANE = 'a'; export const TOGGLE_SEND = 'q'; export const TOGGLE_PLANET_INFO_PANE = 'c'; export const EXIT_PANE = SpecialKey.Escape; // global shortcuts export const TOGGLE_DIAGNOSTICS_PANE = 'i'; export const TOGGLE_EXPLORE = SpecialKey.Space; export const TOGGLE_TARGETTING = '`'; ================================================ FILE: src/Frontend/Utils/TerminalTypes.ts ================================================ export const enum TerminalTextStyle { Green, Sub, Subber, Text, White, Red, Blue, Invisible, Underline, Mythic, } ================================================ FILE: src/Frontend/Utils/TimeUtils.ts ================================================ export function formatDuration(durationMs: number) { if (durationMs < 0) { return ''; } const hours = Math.floor(durationMs / 1000 / 60 / 60); const minutes = Math.floor((durationMs - hours * 60 * 60 * 1000) / 1000 / 60); const seconds = Math.floor((durationMs - hours * 60 * 60 * 1000 - minutes * 60 * 1000) / 1000); return ( timestampSection(hours) + ':' + timestampSection(minutes) + ':' + timestampSection(seconds) ); } function timestampSection(value: number) { return value.toString().padStart(2, '0'); } ================================================ FILE: src/Frontend/Utils/UIEmitter.ts ================================================ import { EventEmitter } from 'events'; export const enum UIEmitterEvent { GamePlanetSelected = 'GamePlanetSelected', CenterPlanet = 'CenterPlanet', WindowResize = 'WindowResize', UIChange = 'UIChange', // whenever you collapse, etc. CanvasMouseDown = 'CanvasMouseDown', CanvasMouseMove = 'CanvasMouseMove', CanvasMouseUp = 'CanvasMouseUp', CanvasMouseOut = 'CanvasMouseOut', CanvasScroll = 'CanvasScroll', WorldMouseDown = 'WorldMouseDown', WorldMouseClick = 'WorldMouseClick', WorldMouseMove = 'WorldMouseMove', WorldMouseUp = 'WorldMouseUp', WorldMouseOut = 'WorldMouseOut', ZoomIn = 'ZoomIn', ZoomOut = 'ZoomOut', SendInitiated = 'SendInitiated', SendCancelled = 'SendCancelled', SendCompleted = 'SendCompleted', DepositArtifact = 'DepositArtifact', DepositToPlanet = 'DepositToPlanet', SelectArtifact = 'SelectArtifact', ShowArtifact = 'ShowArtifact', } class UIEmitter extends EventEmitter { static instance: UIEmitter; private constructor() { super(); } static getInstance(): UIEmitter { if (!UIEmitter.instance) { UIEmitter.instance = new UIEmitter(); } return UIEmitter.instance; } static initialize(): UIEmitter { const uiEmitter = new UIEmitter(); return uiEmitter; } } export default UIEmitter; ================================================ FILE: src/Frontend/Utils/constants.ts ================================================ import * as bigInt from 'big-integer'; // To developer, increase this number to 256. This, in combination with setting `DISABLE_ZK_CHECKS` // in darkforest.toml, will make you mine the map at ULTRA SPEED! // To code reviewer, make sure this does not change in a PR to develop! const MIN_CHUNK_SIZE = 16; /** * @tutorial to speed up the game's background rendering code, it is possible to set this value to * be a higher power of two. This means that smaller chunks will be merged into larger chunks via * the algorithms implemented in {@link ChunkUtils}. * * {@code Math.floor(Math.pow(2, 16))} should be large enough for most. */ const MAX_CHUNK_SIZE = 2 ** 14; const LOCATION_ID_UB = bigInt( '21888242871839275222246405745257275088548364400416034343698204186575808495617' ); export { MIN_CHUNK_SIZE, MAX_CHUNK_SIZE, LOCATION_ID_UB }; export const enum DFZIndex { MenuBar = 4, HoverPlanet = 1001, Modal = 1001, Tooltip = 16000000, Notification = 1000, } ================================================ FILE: src/Frontend/Utils/createDefinedContext.ts ================================================ import React from 'react'; type ContextHookWithProvider = { useDefinedContext: () => T; provider: React.Provider; }; /** * Return a hook and a provider which return a value that must be defined. Normally is difficult * because `React.createContext()` defaults to `undefined`. * * `useDefinedContext()` must be called inside of `provider`, otherwise an error will be thrown. */ export function createDefinedContext(): ContextHookWithProvider { /* This non-null assertion will indeed cause problems if the provider is passed a nullable value, but the below `throw` should catch that case. */ /* eslint-disable-next-line @typescript-eslint/no-non-null-assertion */ const context = React.createContext(undefined!); const useDefinedContext = (): T => { const used = React.useContext(context); // not doing !used because `used` could be a number if (used === undefined) { throw new Error( 'useDefinedContext is undefined! Make sure it is used in a provider and passed a defined value.' ); } return used; }; return { useDefinedContext, provider: context.Provider, }; } ================================================ FILE: src/Frontend/Views/ArtifactLink.tsx ================================================ import { artifactName } from '@darkforest_eth/procedural'; import { Artifact, LocationId } from '@darkforest_eth/types'; import React, { useCallback, useEffect } from 'react'; import { Link } from '../Components/CoreUI'; import { ArtifactDetailsPane } from '../Panes/ArtifactDetailsPane'; import dfstyles from '../Styles/dfstyles'; import { useUIManager } from '../Utils/AppHooks'; import { ModalHandle } from './ModalPane'; export function ArtifactLink({ modal, children, artifact, depositOn, }: { modal?: ModalHandle; artifact: Artifact; children: React.ReactNode | React.ReactNode[]; depositOn?: LocationId; }) { const uiManager = useUIManager(); useEffect(() => { // this is called when the component is unrendered return () => uiManager?.setHoveringOverArtifact(undefined); }, [uiManager]); const onClick = useCallback(() => { uiManager?.setHoveringOverArtifact(undefined); modal && modal.push({ element() { return ( ); }, title: artifactName(artifact), }); }, [artifact, modal, depositOn, uiManager]); return ( { uiManager?.setHoveringOverArtifact(undefined); }} onMouseEnter={() => { uiManager?.setHoveringOverArtifact(artifact.id); }} onMouseLeave={() => { uiManager?.setHoveringOverArtifact(undefined); }} > {children} ); } ================================================ FILE: src/Frontend/Views/ArtifactRow.tsx ================================================ import { isSpaceShip } from '@darkforest_eth/gamelogic'; import { Artifact } from '@darkforest_eth/types'; import React, { useCallback, useEffect, useMemo } from 'react'; import styled, { css } from 'styled-components'; import { ArtifactImage } from '../Components/ArtifactImage'; import { Spacer } from '../Components/CoreUI'; import dfstyles from '../Styles/dfstyles'; import { useUIManager } from '../Utils/AppHooks'; const RowWrapper = styled.div` width: 100%; display: flex; flex-direction: row; justify-content: 'space-around'; align-items: center; overflow-x: scroll; `; const thumbActive = css` border: 1px solid ${dfstyles.colors.border}; background-color: ${dfstyles.colors.border}; `; const StyledArtifactThumb = styled.div<{ active: boolean; enemy: boolean }>` min-width: 2.5em; min-height: 2.5em; width: 2.5em; height: 2.5em; border: 1px solid ${({ enemy }) => (enemy ? dfstyles.colors.dfred : dfstyles.colors.borderDark)}; border-radius: 4px; &:last-child { margin-right: none; } display: inline-flex; flex-direction: row; justify-content: space-around; align-items: center; background: ${dfstyles.colors.artifactBackground}; &:hover { ${thumbActive} cursor: pointer; & > div { filter: brightness(1.2); } } ${({ active }) => active && thumbActive} `; export function ArtifactThumb({ artifact, selectedArtifact, onArtifactChange, }: { selectedArtifact?: Artifact | undefined; onArtifactChange?: (artifact: Artifact | undefined) => void; artifact: Artifact; }) { const uiManager = useUIManager(); const enemy = useMemo(() => { const account = uiManager.getAccount(); if (isSpaceShip(artifact.artifactType)) { return artifact?.controller !== account; } return false; }, [artifact, uiManager]); const click = useCallback(() => { if (!onArtifactChange || enemy) return; if (artifact.id === selectedArtifact?.id) onArtifactChange(undefined); else onArtifactChange(artifact); }, [onArtifactChange, artifact, selectedArtifact, enemy]); useEffect(() => { // this is called when the component is unrendered return () => uiManager?.setHoveringOverArtifact(undefined); }, [uiManager]); return ( { uiManager?.setHoveringOverArtifact(artifact.id); }} onMouseLeave={() => { uiManager?.setHoveringOverArtifact(undefined); }} > ); } export function SelectArtifactRow({ selectedArtifact, onArtifactChange, artifacts, }: { selectedArtifact?: Artifact | undefined; onArtifactChange?: (artifact: Artifact | undefined) => void; artifacts: Artifact[]; }) { return ( {artifacts.length > 0 && artifacts.map((a) => ( ))} ); } ================================================ FILE: src/Frontend/Views/CadetWormhole.tsx ================================================ import React from 'react'; import styled from 'styled-components'; const CadetWormholeContainer = styled.div` height: 100vh; width: 100vw; padding: 3%; display: flex; flex-direction: column; align-items: center; background-image: url(/public/img/cadet-wormhole.png); background-attachment: fixed; background-position: top; background-repeat: no-repeat; /* This is the height of the background-image */ @media (max-height: 1058px) { background-size: contain; } `; export function CadetWormhole({ imgUrl }: { imgUrl: string }) { return ( ); } ================================================ FILE: src/Frontend/Views/DFErrorBoundary.tsx ================================================ import React from 'react'; import styled from 'styled-components'; import { Spacer, Underline } from '../Components/CoreUI'; import { Red } from '../Components/Text'; export class DFErrorBoundary extends React.Component { constructor(props: unknown) { super(props); this.state = { hasError: false }; } static getDerivedStateFromError(_error: Error) { return { hasError: true }; } componentDidCatch(error: Error, _errorInfo: React.ErrorInfo) { console.error(`ui rendering error`); console.error(error); } render() { if (this.state.hasError) { return ( Error! There was an error rendering this UI! Sorry! If you would like to report this error, please send a screenshot of the developer console to the df-feedback channel in our discord. ); } return this.props.children; } } const ErrorBoundaryContent = styled.div` padding: 8px; max-width: 350px; `; ================================================ FILE: src/Frontend/Views/DarkForestTips.tsx ================================================ import _ from 'lodash'; import React, { useCallback, useEffect, useState } from 'react'; import styled from 'styled-components'; import { HeaderText, Spacer, TextButton } from '../Components/CoreUI'; import dfstyles from '../Styles/dfstyles'; const TipText = styled.div` max-width: 500px; word-break: keep-all; text-align: justify; `; const CYCLE_TIPS_INTERVAL = 10 * 1000; export function DarkForestTips({ tips, title, }: { tips: (JSX.Element | string)[]; title?: string; }) { const [tipIndex, setTipIndex] = useState(0); const [_interval, setIntervalHandle] = useState | undefined>(); const incrementTipIndex = useCallback( (increment: number, shouldClearInterval = false) => { if (shouldClearInterval) { setIntervalHandle((interval) => { if (interval) { clearInterval(interval); } return undefined; }); } setTipIndex((tipIndex) => (tipIndex + increment + tips.length) % tips.length); }, [tips.length] ); useEffect(() => { const intervalHandle = setInterval(() => incrementTipIndex(1), CYCLE_TIPS_INTERVAL); setIntervalHandle(intervalHandle); return () => clearInterval(intervalHandle); }, [incrementTipIndex]); return ( {title ?? 'Dark Forest Tips'}{' '} incrementTipIndex(-1, true)}>previous incrementTipIndex(1, true)}>next

{tips[tipIndex]}
); } export function MakeDarkForestTips(tips: string[]) { const shuffledTips = _.shuffle(tips); return ; } const PrevNextContainer = styled.div` float: right; `; const TipsContainer = styled.div` margin-bottom: 8px; background-color: ${dfstyles.colors.backgrounddark}; width: 400px; height: 250px; padding: 16px; border-radius: 3px; overflow: hidden; border: 1px solid ${dfstyles.colors.border}; `; ================================================ FILE: src/Frontend/Views/EmojiPicker.tsx ================================================ import Picker from 'emoji-picker-react'; import React, { useState } from 'react'; import styled from 'styled-components'; import dfstyles from '../Styles/dfstyles'; export function EmojiPicker({ emoji, setEmoji, }: { emoji: string | undefined; setEmoji: (emoji: string) => void; }) { const [pickerOpen, setPickerOpen] = useState(false); return ( setPickerOpen((open) => !open)}> {emoji || '\u00a0'} {pickerOpen && ( { setEmoji(emojiObject.emoji); setPickerOpen(false); }} /> )} ); } const SelectedEmoji = styled.div` display: inline-flex; justify-content: center; align-items: center; border: 1px solid ${dfstyles.colors.borderDark}; border-radius: 3px; cursor: pointer; font-size: 1.5em; width: 30px; height: 30px; margin-right: 8px; &:hover, &:active, &:focus { border: 1px solid ${dfstyles.colors.border}; } `; const EmojiPickerContainer = styled.div` display: inline-block; position: relative; `; const EmojiPickerElementContainer = styled.div` position: absolute; bottom: 100%; right: 100%; .emoji-picker-react { background-color: ${dfstyles.colors.backgroundlight}; box-shadow: none; } .emoji-group::before { background-color: ${dfstyles.colors.backgroundlight}; } .emoji-categories { display: none; } .active-category-indicator-wrapper { display: none; } .emoji-search { display: none; } .content-wrapper::before { display: none; } `; ================================================ FILE: src/Frontend/Views/EmojiPlanetNotification.tsx ================================================ import { Planet, TooltipName } from '@darkforest_eth/types'; import React, { useEffect, useState } from 'react'; import styled from 'styled-components'; import { getEmojiMessage } from '../../Backend/GameLogic/ArrivalUtils'; import { Wrapper } from '../../Backend/Utils/Wrapper'; import { Btn } from '../Components/Btn'; import { LoadingSpinner } from '../Components/LoadingSpinner'; import { Row } from '../Components/Row'; import { Sub } from '../Components/Text'; import { TooltipTrigger } from '../Panes/Tooltip'; import dfstyles from '../Styles/dfstyles'; import { useUIManager } from '../Utils/AppHooks'; import { EmojiPicker } from './EmojiPicker'; const TextWrapper = styled.span` width: 120px; font-size: ${dfstyles.fontSizeXS}; text-align: center; `; export function EmojiPlanetNotification({ wrapper }: { wrapper: Wrapper }) { const gameManager = useUIManager().getGameManager(); const emojiMessage = getEmojiMessage(wrapper.value); const currentEmoji = emojiMessage?.body?.emoji; const [chosenEmoji, setChosenEmoji] = useState(currentEmoji); useEffect(() => { setChosenEmoji(undefined); }, [wrapper?.value?.locationId]); if (wrapper.value?.needsServerRefresh) { return ( ); } else if (emojiMessage !== undefined && currentEmoji !== undefined) { return ( Current emoji: {emojiMessage?.body?.emoji} { if (wrapper.value?.locationId) { gameManager.clearEmoji(wrapper.value.locationId); } }} > Clear Emoji ); } else { const disabled = wrapper.value?.unconfirmedAddEmoji || wrapper.value?.needsServerRefresh || !chosenEmoji; return ( { if (wrapper.value?.locationId && chosenEmoji) { gameManager.setPlanetEmoji(wrapper.value?.locationId, chosenEmoji); } }} > Set Emoji ); } } ================================================ FILE: src/Frontend/Views/GameWindowLayout.tsx ================================================ import { ModalId, ModalName, Setting } from '@darkforest_eth/types'; import React, { useCallback, useEffect, useState } from 'react'; import styled from 'styled-components'; import { BorderlessPane } from '../Components/CoreUI'; import { CanvasContainer, CanvasWrapper, MainWindow, UpperLeft, WindowWrapper, } from '../Components/GameWindowComponents'; import ControllableCanvas from '../Game/ControllableCanvas'; import { ArtifactHoverPane } from '../Panes/ArtifactHoverPane'; import { CoordsPane } from '../Panes/CoordsPane'; import { DiagnosticsPane } from '../Panes/DiagnosticsPane'; import { ExplorePane } from '../Panes/ExplorePane'; import { HelpPane } from '../Panes/HelpPane'; import { HoverPlanetPane } from '../Panes/HoverPlanetPane'; import OnboardingPane from '../Panes/OnboardingPane'; import { PlanetContextPane } from '../Panes/PlanetContextPane'; import { PlanetDexPane } from '../Panes/PlanetDexPane'; import { PlayerArtifactsPane } from '../Panes/PlayerArtifactsPane'; import { PluginLibraryPane } from '../Panes/PluginLibraryPane'; import { PrivatePane } from '../Panes/PrivatePane'; import { SettingsPane } from '../Panes/SettingsPane'; import { TransactionLogPane } from '../Panes/TransactionLogPane'; import { TutorialPane } from '../Panes/TutorialPane'; import { TwitterVerifyPane } from '../Panes/TwitterVerifyPane'; import { ZoomPane } from '../Panes/ZoomPane'; import { useSelectedPlanet, useUIManager } from '../Utils/AppHooks'; import { useOnUp } from '../Utils/KeyEmitters'; import { useBooleanSetting } from '../Utils/SettingsHooks'; import { TOGGLE_DIAGNOSTICS_PANE } from '../Utils/ShortcutConstants'; import { NotificationsPane } from './Notifications'; import { SidebarPane } from './SidebarPane'; import { TopBar } from './TopBar'; export function GameWindowLayout({ terminalVisible, setTerminalVisible, }: { terminalVisible: boolean; setTerminalVisible: (visible: boolean) => void; }) { const uiManager = useUIManager(); const modalManager = uiManager.getModalManager(); const modalPositions = modalManager.getModalPositions(); /** * We use the existence of a window position for a given modal as an indicator * that it should be opened on page load. This is to satisfy the feature of * peristent modal positions across browser sessions for a given account. */ const isModalOpen = useCallback( (modalId: ModalId) => { const pos = modalPositions.get(modalId); if (pos) { return pos.state !== 'closed'; } else { return false; } }, [modalPositions] ); const [helpVisible, setHelpVisible] = useState(isModalOpen(ModalName.Help)); const [transactionLogVisible, setTransactionLogVisible] = useState( isModalOpen(ModalName.TransactionLog) ); const [planetdexVisible, setPlanetdexVisible] = useState( isModalOpen(ModalName.PlanetDex) ); const [playerArtifactsVisible, setPlayerArtifactsVisible] = useState( isModalOpen(ModalName.YourArtifacts) ); const [twitterVerifyVisible, setTwitterVerifyVisible] = useState( isModalOpen(ModalName.TwitterVerify) ); const [settingsVisible, setSettingsVisible] = useState(isModalOpen(ModalName.Settings)); const [privateVisible, setPrivateVisible] = useState(isModalOpen(ModalName.Private)); const [pluginsVisible, setPluginsVisible] = useState(isModalOpen(ModalName.Plugins)); const [diagnosticsVisible, setDiagnosticsVisible] = useState( isModalOpen(ModalName.Diagnostics) ); const [modalsContainer, setModalsContainer] = useState(); const modalsContainerCB = useCallback((node) => { setModalsContainer(node); }, []); const [onboardingVisible, setOnboardingVisible] = useBooleanSetting(uiManager, Setting.NewPlayer); const tutorialHook = useBooleanSetting(uiManager, Setting.TutorialOpen); const selected = useSelectedPlanet(uiManager).value; const [selectedPlanetVisible, setSelectedPlanetVisible] = useState(!!selected); const [userTerminalVisibleSetting, setTerminalVisibleSetting] = useBooleanSetting( uiManager, Setting.TerminalVisible ); useEffect(() => { uiManager.setOverlayContainer(modalsContainer); }, [uiManager, modalsContainer]); const account = uiManager.getAccount(); useEffect(() => { if (uiManager.getAccount()) { setTerminalVisible(uiManager.getBooleanSetting(Setting.TerminalVisible)); } }, [account, uiManager, setTerminalVisible]); useEffect(() => { if (userTerminalVisibleSetting !== terminalVisible) { setTerminalVisibleSetting(terminalVisible); } }, [userTerminalVisibleSetting, setTerminalVisibleSetting, terminalVisible]); useEffect(() => setSelectedPlanetVisible(!!selected), [selected, setSelectedPlanetVisible]); useOnUp( TOGGLE_DIAGNOSTICS_PANE, useCallback(() => { setDiagnosticsVisible((value) => !value); }, [setDiagnosticsVisible]) ); return ( {/* all modals rendered into here */}
setHelpVisible(false)} /> setTransactionLogVisible(false)} /> setPlanetdexVisible(false)} /> setTwitterVerifyVisible(false)} /> setSettingsVisible(false)} onOpenPrivate={() => setPrivateVisible(true)} /> setPrivateVisible(false)} /> setPlayerArtifactsVisible(false)} /> setSelectedPlanetVisible(false)} /> setDiagnosticsVisible(false)} /> {modalsContainer && ( setPluginsVisible(false)} /> )}
setOnboardingVisible(false)} />
); } const TopBarPaneContainer = styled.div` display: flex; justify-content: center; align-items: center; width: 100vw; position: absolute; top: 0; left: 0; `; ================================================ FILE: src/Frontend/Views/GenericErrorBoundary.tsx ================================================ import React from 'react'; import styled from 'styled-components'; import { Red } from '../Components/Text'; interface GenericErrorBoundaryProps { errorMessage: string; } export class GenericErrorBoundary extends React.Component< GenericErrorBoundaryProps, { hasError: boolean } > { constructor(props: GenericErrorBoundaryProps) { super(props); this.state = { hasError: false }; } static getDerivedStateFromError(_error: Error) { return { hasError: true }; } componentDidCatch(error: Error, _errorInfo: React.ErrorInfo) { console.error(`ui rendering error`); console.error(error); } render() { if (this.state.hasError) { return ( {this.props.errorMessage} ); } return this.props.children; } } const ErrorBoundaryContent = styled.div` padding: 8px; max-width: 350px; `; ================================================ FILE: src/Frontend/Views/LandingPageRoundArt.tsx ================================================ import React from 'react'; import styled from 'styled-components'; import { TwitterLink } from '../Components/Labels/Labels'; import { Smaller, Text } from '../Components/Text'; export function LandingPageRoundArt() { return ( Art by {' '} ); } const Container = styled.div` display: flex; justify-content: center; align-items: center; `; const ImgContainer = styled.div` display: inline-block; text-align: right; width: 750px; max-width: 80vw; @media only screen and (max-device-width: 1000px) { width: 100%; max-width: 100%; padding: 8px; font-size: 80%; } `; const LandingPageRoundArtImg = styled.img``; ================================================ FILE: src/Frontend/Views/Leaderboard.tsx ================================================ import { ArtifactRarity, Leaderboard } from '@darkforest_eth/types'; import React, { useEffect, useState } from 'react'; import styled from 'styled-components'; import { Spacer } from '../Components/CoreUI'; import { TwitterLink } from '../Components/Labels/Labels'; import { LoadingSpinner } from '../Components/LoadingSpinner'; import { Red } from '../Components/Text'; import { TextPreview } from '../Components/TextPreview'; import { RarityColors } from '../Styles/Colors'; import dfstyles from '../Styles/dfstyles'; import { useLeaderboard } from '../Utils/AppHooks'; import { formatDuration } from '../Utils/TimeUtils'; import { GenericErrorBoundary } from './GenericErrorBoundary'; import { Table } from './Table'; export function LeadboardDisplay() { const { leaderboard, error } = useLeaderboard(); const errorMessage = 'Error Loading Leaderboard'; return ( {!leaderboard && !error && } {leaderboard && } {error && {errorMessage}} ); } function scoreToString(score?: number | null) { if (score === null || score === undefined) { return 'n/a'; } score = Math.floor(score); if (score < 10000) { return score + ''; } return score.toLocaleString(); } // pass in either an address, or a twitter handle. this function will render the appropriate // component function playerToEntry(playerStr: string, color: string) { // if this is an address if (playerStr.startsWith('0x') && playerStr.length === 42) { return ; } return ; } function getRankColor([rank, score]: [number, number | undefined]) { if (score === undefined || score === null) { return dfstyles.colors.subtext; } if (rank === 0) { return RarityColors[ArtifactRarity.Mythic]; } if (rank === 1 || rank === 2) { return RarityColors[ArtifactRarity.Legendary]; } if (rank >= 3 && rank <= 6) { return RarityColors[ArtifactRarity.Epic]; } if (rank >= 7 && rank <= 14) { return RarityColors[ArtifactRarity.Rare]; } if (rank >= 15 && rank <= 30) { return dfstyles.colors.dfgreen; } if (rank >= 31 && rank <= 62) { return 'white'; } return dfstyles.colors.subtext; } function LeaderboardTable({ rows }: { rows: Array<[string, number | undefined]> }) { return ( place, player, score, ]} rows={rows} columns={[ (row: [string, number], i) => ( {row[1] === undefined || row[1] === null ? 'unranked' : i + 1 + '.'} ), (row: [string, number | undefined], i) => { const color = getRankColor([i, row[1]]); return {playerToEntry(row[0], color)}; }, (row: [string, number], i) => { return ( {scoreToString(row[1])} ); }, ]} /> ); } // TODO: update this each round, or pull from contract constants const roundEndTimestamp = '2022-03-01T05:00:00.000Z'; const roundEndTime = new Date(roundEndTimestamp).getTime(); function CountDown() { const [str, setStr] = useState(''); const update = () => { const timeUntilEndms = roundEndTime - new Date().getTime(); if (timeUntilEndms <= 0) { setStr('yes'); } else { setStr(formatDuration(timeUntilEndms)); } }; useEffect(() => { const interval = setInterval(() => { update(); }, 499); update(); return () => clearInterval(interval); }, []); return <>{str}; } function LeaderboardBody({ leaderboard }: { leaderboard: Leaderboard }) { const rankedPlayers = leaderboard.entries.filter( (entry) => entry.score !== undefined && entry.score > 0 ); leaderboard.entries.sort((a, b) => { if (typeof a.score !== 'number' && typeof b.score !== 'number') { return 0; } else if (typeof a.score !== 'number') { return 1; } else if (typeof b.score !== 'number') { return -1; } return b.score - a.score; }); const rows: [string, number | undefined][] = leaderboard.entries.map((entry) => { if (typeof entry.twitter === 'string') { return [entry.twitter, entry.score]; } return [entry.ethAddress, entry.score]; }); return (
); } const Cell = styled.div` padding: 4px 8px; color: ${dfstyles.colors.text}; `; const TableContainer = styled.div` display: inline-block; border-radius: 2px 2px 0 0px; border-bottom: none; padding: 16px; `; const StatsTableContainer = styled.div` display: flex; justify-content: center; align-items: center; color: ${dfstyles.colors.text}; `; const StatsTable = styled.table` td { padding: 4px 8px; &:first-child { text-align: right; color: ${dfstyles.colors.subtext}; } &:last-child { text-align: left; } } `; ================================================ FILE: src/Frontend/Views/ModalIcon.tsx ================================================ import { ModalName } from '@darkforest_eth/types'; import React from 'react'; import styled from 'styled-components'; import { Hook } from '../../_types/global/GlobalTypes'; import { Spacer } from '../Components/CoreUI'; import { Icon, IconType } from '../Components/Icons'; import { MaybeShortcutButton } from '../Components/MaybeShortcutButton'; const ModalIconText = styled.span` flex-grow: 1; display: flex; justify-content: center; align-items: center; flex-direction: row; height: 26px; `; const icon = (modal: ModalName): React.ReactNode => { if (modal === ModalName.Help) return ; else if (modal === ModalName.PlanetDetails) return ; else if (modal === ModalName.Leaderboard) return ; else if (modal === ModalName.PlanetDex) return ; else if (modal === ModalName.UpgradeDetails) return ; else if (modal === ModalName.TwitterVerify) return ; else if (modal === ModalName.Broadcast) return ; else if (modal === ModalName.MapShare) return ; else if (modal === ModalName.ManageAccount) return ; else if (modal === ModalName.Hats) return ; else if (modal === ModalName.Settings) return ; else if (modal === ModalName.Plugins) return ; else if (modal === ModalName.YourArtifacts) return ; else if (modal === ModalName.WithdrawSilver) return ; else if (modal === ModalName.TransactionLog) return ; return T; }; /** * A button which allows you to open a modal. */ export function ModalToggleButton({ modal, hook: [_active, setActive], text, style, ...props }: { modal: ModalName; hook: Hook; text?: string; style?: React.CSSProperties; } & React.ComponentProps) { const toggle = () => { setActive((b: boolean) => !b); }; return ( {icon(modal)} {text !== undefined && ( <> {text} )} ); } ================================================ FILE: src/Frontend/Views/ModalPane.tsx ================================================ import { ModalId } from '@darkforest_eth/types'; import React, { useCallback, useEffect, useLayoutEffect, useMemo, useState } from 'react'; import styled from 'styled-components'; import { Btn } from '../Components/Btn'; import { EmSpacer, Spacer, Title, Truncate } from '../Components/CoreUI'; import { PaneProps } from '../Components/GameWindowComponents'; import { MaybeShortcutButton } from '../Components/MaybeShortcutButton'; import { DarkForestModal, Modal, PositionChangedEvent } from '../Components/Modal'; import dfstyles from '../Styles/dfstyles'; import { useUIManager } from '../Utils/AppHooks'; import { useEmitterValue } from '../Utils/EmitterHooks'; import { MODAL_BACK_SHORTCUT } from '../Utils/ShortcutConstants'; import { DFErrorBoundary } from './DFErrorBoundary'; function InformationSection({ children, hide }: { children: React.ReactNode; hide: () => void }) { return ( {children} close help ); } const BtnContainer = styled.div` display: flex; justify-content: flex-end; align-items: center; margin-top: 8px; `; const InfoSectionContent = styled.div` text-align: justify; color: ${dfstyles.colors.subtext}; `; export type ModalProps = PaneProps & { title: string | React.ReactNode; style?: CSSStyleDeclaration & React.CSSProperties; visible: boolean; onClose: () => void; id: ModalId; hideClose?: boolean; helpContent?: () => React.ReactNode; width?: string; initialPosition?: { x: number; y: number; }; }; /** * A modal has a {@code content}, and also optionally many {@link ModalFrames} pushed on top of it. */ export interface ModalFrame { title: string; element: () => React.ReactElement; helpContent?: React.ReactElement; } /** * @todo Add things like open, close, set position, etc. */ export interface ModalHandle { push(frame: ModalFrame): void; popAll(): void; pop(): void; id: string; isActive: boolean; } export function ModalPane({ style, children, title, visible, onClose, hideClose, helpContent, width, initialPosition, id, }: ModalProps) { const uiManager = useUIManager(); const modalManager = uiManager.getModalManager(); const windowPositions = modalManager.getModalPositions(); const modalPosition = id ? windowPositions.get(id) : undefined; const activeModalId = useEmitterValue(modalManager.activeModalId$, undefined); const isActive = id === activeModalId; const [frames, setFrames] = useState([]); const [renderedFrame, setRenderedFrame] = useState(); const [renderedFrameHelp, setRenderedFrameHelp] = useState(); const [minimized, setMinimized] = useState(modalPosition?.state === 'minimized'); const [modalIndex, setModalIndex] = useState(() => modalManager.getIndex()); const push = useCallback(() => { modalManager.activeModalId$.publish(id); setModalIndex(modalManager.getIndex()); }, [modalManager, id]); const [showingInformationSection, setShowingInformationSection] = useState(false); const onMouseDown = useCallback( (e: Event & React.MouseEvent) => { push(); e.stopPropagation(); }, [push] ); const initialPos = modalPosition || initialPosition; const showingHelp = helpContent !== undefined && showingInformationSection; const api: ModalHandle = useMemo( () => ({ pop: () => { setFrames((frames) => { if (frames.length === 0) return frames; frames = [...frames]; frames.pop(); return frames; }); }, push: (args: ModalFrame) => { setFrames((frames) => { frames = [...frames]; frames.push(args); return frames; }); }, popAll: () => { setFrames([]); }, id, isActive, }), [id, isActive] ); // push to top useLayoutEffect(() => { push(); }, [visible, modalManager, push]); useEffect(() => { const timeout = setTimeout(() => { const topFrame = frames[frames.length - 1]; setRenderedFrame((topFrame && topFrame.element()) || undefined); setRenderedFrameHelp((topFrame && topFrame.helpContent) || undefined); }, 0); return () => clearTimeout(timeout); }, [frames, api]); const onPositionChanged = useCallback( (evt: PositionChangedEvent) => { if (!id) return; if (visible) { modalManager.setModalPosition(id, { x: evt.coords.x, y: evt.coords.y, state: minimized ? 'minimized' : 'open', modalId: id, }); } }, [visible, modalManager, minimized, id] ); useEffect(() => { if (!id) return; if (!visible) { modalManager.setModalState(id, 'closed'); } }, [visible, modalManager, id]); let content; if (showingHelp) { content = ( setShowingInformationSection(false)}> {renderedFrameHelp || (helpContent && helpContent())} ); } else if (renderedFrame) { content = {renderedFrame}; } else { content = ( {typeof children === 'function' ? children(api) : children} ); } function getFrameTitle(args?: ModalFrame) { if (!args) return undefined; return `${args.title}`; } const modalTitleElement = typeof title === 'string' ? title : title(frames.length > 0); const allSubModalTitleElements = []; if (frames.length > 0) { allSubModalTitleElements.push(getFrameTitle(frames[frames.length - 1])); } if (!visible) { return null; } else { return ( {frames.length > 0 && ( api.pop()} onShortcutPressed={() => api.pop()} shortcutKey={MODAL_BACK_SHORTCUT} shortcutText={MODAL_BACK_SHORTCUT} > back )} <Truncate maxWidth={allSubModalTitleElements.length !== 0 ? '50px' : undefined}> {modalTitleElement} </Truncate> {allSubModalTitleElements.length !== 0 && <EmSpacer width={0.5} />} {allSubModalTitleElements} {/* render the 'close' and 'help me' buttons, depending on whether or not they're relevant */}
{helpContent !== undefined && !minimized && ( <> setShowingInformationSection((showing) => !showing)}> help )} setMinimized((minimized: boolean) => !minimized)}> {minimized ? 'maximize' : 'minimize'} {!hideClose && ( <> onClose()}> close )}
{content}
); } } ================================================ FILE: src/Frontend/Views/NetworkHealth.tsx ================================================ import { AutoGasSetting, TooltipName } from '@darkforest_eth/types'; import React from 'react'; import { Spread } from '../Components/CoreUI'; import { Sub, Text } from '../Components/Text'; import { TooltipTrigger } from '../Panes/Tooltip'; import { useUIManager } from '../Utils/AppHooks'; import { useEmitterValue } from '../Utils/EmitterHooks'; export function NetworkHealth() { const uiManager = useUIManager(); const networkHealth = useEmitterValue(uiManager.getGameManager().networkHealth$, undefined); if (!networkHealth || networkHealth.length === 0) { return <>; } return ( <>
{networkHealth && Object.values(AutoGasSetting) .map((setting) => networkHealth.find((entry) => entry[0] === setting)) .map( (entry) => entry && ( ) )} ); } const SettingNames = { [AutoGasSetting.Average]: 'avg', [AutoGasSetting.Slow]: 'slo', [AutoGasSetting.Fast]: 'fst', }; function NetworkHealthForGasSetting({ setting, confirmationWaitTime, }: { setting: AutoGasSetting; confirmationWaitTime: number; }) { return ( {SettingNames[setting]}: {(confirmationWaitTime / 1000).toFixed(2)}s ); } ================================================ FILE: src/Frontend/Views/Notifications.tsx ================================================ import { Setting } from '@darkforest_eth/types'; import _ from 'lodash'; import React, { useEffect, useState } from 'react'; import styled from 'styled-components'; import NotificationManager, { NotificationInfo, NotificationManagerEvent, NotificationType, } from '../Game/NotificationManager'; import dfstyles, { snips } from '../Styles/dfstyles'; import { useUIManager } from '../Utils/AppHooks'; import { DFZIndex } from '../Utils/constants'; /** * React component which represents a single notification. Can be hovered over for more info, or * clicked on to dismiss. */ function Notification({ notif, onClick, style, }: { notif: NotificationInfo; onClick: () => void; style?: React.CSSProperties; }) { const { message, icon } = notif; const [showing, setShowing] = useState(false); return ( setShowing(false)}> setShowing(true)} onClick={onClick} style={{ ...style, background: notif.color }} > {icon} {showing && {message}} ); } /** * React component in charge of listening for new notifications and displaying them interactively to * the user. */ export function NotificationsPane() { const [notifs, setNotifs] = useState>([]); const uiManager = useUIManager(); // listen for new notifs useEffect(() => { const notifManager = NotificationManager.getInstance(); const addNotif = (notif: NotificationInfo) => { const notifMove = uiManager.getBooleanSetting(Setting.MoveNotifications); if (!notifMove && notif.type === NotificationType.Tx && notif.txData?.methodName === 'move') return; setNotifs((arr) => { const newArr = _.clone(arr); for (let i = 0; i < arr.length; i++) { if (arr[i].id === notif.id) { newArr[i] = notif; return newArr; } } return newArr.concat([notif]); }); }; const clearNotif = (id: string) => { setNotifs((arr) => arr.filter((n) => n.id !== id)); }; notifManager.on(NotificationManagerEvent.ClearNotification, clearNotif); notifManager.on(NotificationManagerEvent.Notify, addNotif); return () => { notifManager.removeListener(NotificationManagerEvent.ClearNotification, clearNotif); notifManager.removeListener(NotificationManagerEvent.Notify, addNotif); }; }, [uiManager]); // creates a callback for a notif which removes itself const getRemove = (notif: NotificationInfo): (() => void) => { return (): void => { const copy = _.clone(notifs); for (let i = 0; i < copy.length; i++) { if (copy[i].id === notif.id) copy.splice(i, 1); } setNotifs(copy); }; }; return ( {notifs.slice(0, Math.min(10, notifs.length)).map((el, i) => ( ))} ); } const NOTIF_SIZE = '4em'; const MARGIN = '8px'; const StyledNotification = styled.div` margin: ${MARGIN}; display: flex; flex-direction: row-reverse; justify-content: flex-start; &:hover { z-index: ${DFZIndex.Tooltip}; } `; /** * Element which contains the notification-dependent icon. User can hover over this to display more * info about the notification. */ const NotificationIconContainer = styled.div` width: ${NOTIF_SIZE}; height: ${NOTIF_SIZE}; border-radius: 8px; border: 1px solid ${dfstyles.colors.text}; background: ${({ color }) => color || dfstyles.colors.backgroundlighter}; overflow: hidden; display: flex; flex-grow: 0; flex-shrink: 0; flex-direction: row; justify-content: space-around; align-items: center; `; /** * Element which contains the information which is attached to the notification. Only shown for a * notification if the user is hovering over the notification's {@link NotificationIconContainer}. */ const NotificationContent = styled.div` border-radius: ${snips.roundedBordersWithEdge}; min-height: ${NOTIF_SIZE}; height: ${NOTIF_SIZE}; min-width: 3em; max-width: 50em; overflow: scroll; margin-right: ${MARGIN}; background: ${dfstyles.colors.background}; padding: 0.5em 1em; border-radius: 2px; display: flex; flex-direction: row; justify-content: flex-start; align-items: center; z-index: ${DFZIndex.Tooltip}; `; /** * The element which contains all the notifications */ const NotificationsContainer = styled.div` position: absolute; top: 0; right: 0; z-index: ${DFZIndex.Notification}; `; ================================================ FILE: src/Frontend/Views/Paused.tsx ================================================ import { TooltipName } from '@darkforest_eth/types'; import React from 'react'; import styled from 'styled-components'; import { TooltipTrigger } from '../Panes/Tooltip'; import { usePaused } from '../Utils/AppHooks'; export function Paused() { const paused = usePaused(); if (!paused) { return <>; } return ( The game is currently paused so that everyone can spawn and then start playing at the same time. You can still mine the map, but you can't make any moves. } name={TooltipName.Empty} > PAUSED ); } const PausedContainer = styled.div` font-size: 4em; text-align: center; `; ================================================ FILE: src/Frontend/Views/PlanetCard.tsx ================================================ import { isLocatable } from '@darkforest_eth/gamelogic'; import { getPlanetName } from '@darkforest_eth/procedural'; import { Planet, TooltipName } from '@darkforest_eth/types'; import React from 'react'; import styled from 'styled-components'; import { Wrapper } from '../../Backend/Utils/Wrapper'; import { StatIdx } from '../../_types/global/GlobalTypes'; import { AlignCenterHorizontally, EmSpacer, InlineBlock, SpreadApart } from '../Components/CoreUI'; import { Icon, IconType } from '../Components/Icons'; import { AccountLabel } from '../Components/Labels/Labels'; import { DefenseText, EnergyGrowthText, JunkText, PlanetBiomeTypeLabelAnim, PlanetEnergyLabel, PlanetLevel, PlanetRank, PlanetSilverLabel, RangeText, SilverGrowthText, SpeedText, } from '../Components/Labels/PlanetLabels'; import { Sub } from '../Components/Text'; import { PlanetIcons } from '../Renderers/PlanetscapeRenderer/PlanetIcons'; import dfstyles, { snips } from '../Styles/dfstyles'; import { useActiveArtifact, usePlanetArtifacts, useUIManager } from '../Utils/AppHooks'; import { useEmitterValue } from '../Utils/EmitterHooks'; import { SelectArtifactRow } from './ArtifactRow'; import { Halved, PlanetActiveArtifact, RowTip, TimesTwo, TitleBar } from './PlanetCardComponents'; export function PlanetCardTitle({ planet, small, }: { planet: Wrapper; small?: boolean; }) { if (!planet.value) return <>; if (small) return <>{getPlanetName(planet.value)}; return ( {getPlanetName(planet.value)} ); } const ElevatedContainer = styled.div` ${snips.roundedBordersWithEdge} border-color: ${dfstyles.colors.borderDarker}; background-color: ${dfstyles.colors.backgroundlight}; margin-top: 8px; margin-bottom: 8px; font-size: 85%; `; /** Preview basic planet information - used in `PlanetContextPane` and `HoverPlanetPane` */ export function PlanetCard({ planetWrapper: p, standalone, }: { planetWrapper: Wrapper; standalone?: boolean; }) { const uiManager = useUIManager(); const active = useActiveArtifact(p, uiManager); const planet = p.value; const artifacts = usePlanetArtifacts(p, uiManager); const spaceJunkEnabled = uiManager.getSpaceJunkEnabled(); const isAbandoning = useEmitterValue(uiManager.isAbandoning$, uiManager.isAbandoning()); if (!planet || !isLocatable(planet)) return <>; return ( <> {standalone && ( )}
{active && ( <> )}
{planet?.bonus && planet.bonus[StatIdx.EnergyCap] && }
{planet?.bonus && planet.bonus[StatIdx.EnergyGro] && }
{planet?.bonus && planet.bonus[StatIdx.Defense] && }
{planet?.bonus && planet.bonus[StatIdx.Speed] && }
{planet?.bonus && planet.bonus[StatIdx.Range] && }
{spaceJunkEnabled && (
{planet?.bonus && planet.bonus[StatIdx.SpaceJunk] && }
)}
{standalone && ( <> owner )}
); } const StatRow = styled(AlignCenterHorizontally)` ${snips.roundedBorders} display: inline-block; box-sizing: border-box; width: 100%; /* Set the Icon color to something a little dimmer */ --df-icon-color: ${dfstyles.colors.subtext}; `; ================================================ FILE: src/Frontend/Views/PlanetCardComponents.tsx ================================================ import { Artifact, Planet, TooltipName } from '@darkforest_eth/types'; import React from 'react'; import styled from 'styled-components'; import { ArtifactBiomeText, ArtifactRarityLabelAnim, ArtifactTypeText, } from '../Components/Labels/ArtifactLabels'; import { Sub, White } from '../Components/Text'; import { TooltipTrigger } from '../Panes/Tooltip'; import dfstyles from '../Styles/dfstyles'; const BonusStyle = styled.span` color: ${dfstyles.colors.dfgreen}; font-size: 0.8em; vertical-align: center; line-height: 1.5em; margin-left: 8px; `; export const TimesTwo = () => x2; export const Halved = () => %2; export const RowTip = ({ name, children }: { name: TooltipName; children: React.ReactNode }) => ( {children} ); export const TitleBar = styled.div` height: 2em; padding: 0.25em 0.5em; display: flex; flex-direction: row; justify-content: space-between; color: ${dfstyles.colors.subtext}; border-bottom: 1px solid ${dfstyles.colors.border}; `; const StyledPlanetActiveArtifact = styled.div<{ planet: Planet | undefined }>` display: flex; flex-direction: row; justify-content: flex-start; align-items: center; color: ${dfstyles.colors.text}; `; export function PlanetActiveArtifact({ artifact, planet, }: { artifact: Artifact; planet: Planet | undefined; }) { return ( Active Artifact:{' '} {' '} {' '} ); } ================================================ FILE: src/Frontend/Views/PlanetLink.tsx ================================================ import { isLocatable } from '@darkforest_eth/gamelogic'; import { Planet } from '@darkforest_eth/types'; import React from 'react'; import { Link } from '../Components/CoreUI'; import dfstyles from '../Styles/dfstyles'; import { useUIManager } from '../Utils/AppHooks'; import UIEmitter, { UIEmitterEvent } from '../Utils/UIEmitter'; export function PlanetLink({ planet, children }: { planet: Planet; children: React.ReactNode }) { const uiManager = useUIManager(); const uiEmitter = UIEmitter.getInstance(); return ( { if (isLocatable(planet)) { uiManager?.setSelectedPlanet(planet); uiEmitter.emit(UIEmitterEvent.CenterPlanet, planet); } }} onMouseEnter={() => { if (isLocatable(planet)) uiManager?.setHoveringOverPlanet(planet, false); }} onMouseLeave={() => { uiManager?.setHoveringOverPlanet(undefined, false); }} > {children} ); } ================================================ FILE: src/Frontend/Views/PlanetNotifications.tsx ================================================ import { isLocatable } from '@darkforest_eth/gamelogic'; import { EthAddress, Planet } from '@darkforest_eth/types'; import React from 'react'; import styled from 'styled-components'; import { GameObjects } from '../../Backend/GameLogic/GameObjects'; import { Wrapper } from '../../Backend/Utils/Wrapper'; import { AccountLabel } from '../Components/Labels/Labels'; import { Row } from '../Components/Row'; import { Sub } from '../Components/Text'; import dfstyles from '../Styles/dfstyles'; import { EmojiPlanetNotification } from './EmojiPlanetNotification'; export const enum PlanetNotifType { PlanetCanUpgrade, Claimed, DistanceFromCenter, CanAddEmoji, } const StyledPlanetNotifications = styled.div` font-size: ${dfstyles.fontSizeXS}; `; export function getNotifsForPlanet( planet: Planet | undefined, account: EthAddress | undefined ): PlanetNotifType[] { const notifs: PlanetNotifType[] = []; if (!planet) return notifs; if (planet?.owner === account && account !== undefined) { if (GameObjects.planetCanUpgrade(planet)) notifs.push(PlanetNotifType.PlanetCanUpgrade); if (process.env.DF_WEBSERVER_URL) notifs.push(PlanetNotifType.CanAddEmoji); } return notifs; } function EmojiRow({ wrapper }: { wrapper: Wrapper }) { return ; } const PlanetCanUpgradeRow = () => ( This planet can upgrade! ); export const DistanceFromCenterRow = ({ planet }: { planet: Wrapper }) => planet.value && isLocatable(planet.value) ? ( Distance From Center:{' '} {Math.floor( Math.sqrt( planet.value.location.coords.x ** 2 + planet.value.location.coords.y ** 2 + 0.001 ) ).toLocaleString()} ) : ( Unclaimed ); export const PlanetClaimedRow = ({ planet }: { planet: Wrapper }) => planet.value?.claimer ? ( Claimed by{' '} ) : ( Unclaimed ); function renderNotification(notif: PlanetNotifType, planet: Wrapper) { switch (notif) { case PlanetNotifType.PlanetCanUpgrade: return ; case PlanetNotifType.CanAddEmoji: return ; case PlanetNotifType.Claimed: return ; case PlanetNotifType.DistanceFromCenter: return ; default: return null; } } export function PlanetNotifications({ notifs, planet, }: { notifs: PlanetNotifType[]; planet: Wrapper; }) { return ( {notifs.map((notif, i) => (
{renderNotification(notif, planet)}
))}
); } ================================================ FILE: src/Frontend/Views/SendResources.tsx ================================================ import { formatNumber, isSpaceShip } from '@darkforest_eth/gamelogic'; import { isUnconfirmedMoveTx, isUnconfirmedReleaseTx } from '@darkforest_eth/serde'; import { Artifact, artifactNameFromArtifact, Planet, TooltipName } from '@darkforest_eth/types'; import React, { useCallback } from 'react'; import styled from 'styled-components'; import { Wrapper } from '../../Backend/Utils/Wrapper'; import { StatIdx } from '../../_types/global/GlobalTypes'; import { Btn } from '../Components/Btn'; import { Icon, IconType } from '../Components/Icons'; import { LoadingSpinner } from '../Components/LoadingSpinner'; import { MaybeShortcutButton } from '../Components/MaybeShortcutButton'; import { Row } from '../Components/Row'; import { Slider } from '../Components/Slider'; import { LongDash, Subber } from '../Components/Text'; import { TooltipTrigger } from '../Panes/Tooltip'; import dfstyles from '../Styles/dfstyles'; import { useAccount, usePlanetInactiveArtifacts, useUIManager } from '../Utils/AppHooks'; import { useEmitterValue } from '../Utils/EmitterHooks'; import { useOnUp } from '../Utils/KeyEmitters'; import { TOGGLE_ABANDON, TOGGLE_SEND } from '../Utils/ShortcutConstants'; import { SelectArtifactRow } from './ArtifactRow'; const StyledSendResources = styled.div` display: flex; flex-direction: column; gap: 8px; `; const StyledShowPercent = styled.div` display: inline-block; & > span:first-child { width: 3em; text-align: right; margin-right: 1em; } & > span:last-child { color: ${dfstyles.colors.subtext}; & > span { ${dfstyles.prefabs.noselect}; &:hover { color: ${dfstyles.colors.text}; cursor: pointer; } &:first-child { margin-right: 0.5em; } } } `; function ShowPercent({ value, setValue }: { value: number; setValue: (x: number) => void }) { return ( {value}% setValue(value - 1)}> setValue(value + 1)}>+ ); } const ResourceRowDetails = styled.div` display: inline-flex; align-items: center; gap: 4px; `; function ResourceBar({ isSilver, selected, value, setValue, disabled, }: { isSilver?: boolean; selected: Planet | undefined; value: number; setValue: (x: number) => void; disabled?: boolean; }) { const getResource = useCallback( (val: number) => { if (!selected) return ''; const resource = isSilver ? selected.silver : selected.energy; return formatNumber((val / 100) * resource); }, [selected, isSilver] ); return ( <> {getResource(value)} {isSilver ? 'silver' : 'energy'} ) => { setValue(parseInt(e.target.value, 10)); }} /> ); } function AbandonButton({ planet, abandoning, toggleAbandoning, disabled, }: { planet?: Planet; abandoning: boolean; toggleAbandoning: () => void; disabled?: boolean; }) { const uiManager = useUIManager(); if (!planet) return null; let junk = uiManager.getDefaultSpaceJunkForPlanetLevel(planet?.planetLevel); if (planet.bonus[StatIdx.SpaceJunk]) junk /= 2; /* Explicitly avoid binding to `onShortcutPressed` so we can support sending on subpanes */ return ( {abandoning ? 'Abandoning' : `Abandon Planet (-${junk}) space junk`} ); } function SendRow({ toggleSending, artifact, sending, abandoning, disabled = false, }: { toggleSending: () => void; artifact: Artifact | undefined; sending: boolean; abandoning?: boolean; disabled?: boolean; }) { let content = 'Send'; if (artifact) { const artifactName = artifactNameFromArtifact(artifact); if (isSpaceShip(artifact.artifactType)) { // Call it "Move" with a spaceship, instead of "Send" content = `Move ${artifactName}`; } else { // Only add the "+" if we are sending Energy & Artifact content += ` + ${artifactName}`; } } if (abandoning) { content += ' and Abandon'; } /* Explicitly avoid binding to `onShortcutPressed` so we can support sending on subpanes */ return ( {content} ); } export function SendResources({ planetWrapper: p, onToggleSendForces, onToggleAbandon, }: { planetWrapper: Wrapper; onToggleSendForces: () => void; onToggleAbandon: () => void; }) { const uiManager = useUIManager(); const account = useAccount(uiManager); const owned = p.value?.owner === account; const locationId = p?.value?.locationId; const isSendingShip = uiManager.isSendingShip(locationId); const isAbandoning = useEmitterValue(uiManager.isAbandoning$, false); const isSendingForces = useEmitterValue(uiManager.isSending$, false); const energySending = uiManager.getForcesSending(locationId); const silverSending = uiManager.getSilverSending(locationId); const artifactSending = uiManager.getArtifactSending(locationId); const disableSliders = isSendingShip || isAbandoning; const updateEnergySending = useCallback( (energyPercent) => { if (!locationId) return; uiManager.setForcesSending(locationId, energyPercent); }, [uiManager, locationId] ); const updateSilverSending = useCallback( (silverPercent) => { if (!locationId) return; uiManager.setSilverSending(locationId, silverPercent); }, [uiManager, locationId] ); const updateArtifactSending = useCallback( (sendArtifact) => { if (!locationId) return; uiManager.setArtifactSending(locationId, sendArtifact); }, [uiManager, locationId] ); // this variable is an array of 10 elements. each element is a key. whenever the user presses a // key, we set the amount of energy that we're sending to be proportional to how late in the array // that key is const energyShortcuts = '1234567890'.split(''); // same as above, except for silver const silverShortcuts = '!@#$%^&*()'.split(''); // for each of the above keys, we set up a listener that is triggered whenever that key is // pressed, and sets the corresponding resource sending amount for (let i = 0; i < energyShortcuts.length; i++) { // eslint-disable-next-line react-hooks/rules-of-hooks useOnUp(energyShortcuts[i], () => updateEnergySending((i + 1) * 10), [updateEnergySending]); // eslint-disable-next-line react-hooks/rules-of-hooks useOnUp(silverShortcuts[i], () => updateSilverSending((i + 1) * 10), [updateSilverSending]); } useOnUp( '-', () => { updateEnergySending(uiManager.getForcesSending(locationId) - 10); }, [uiManager, locationId, updateEnergySending] ); useOnUp( '=', () => { updateEnergySending(uiManager.getForcesSending(locationId) + 10); }, [uiManager, locationId, updateEnergySending] ); useOnUp( '_', () => { updateSilverSending(uiManager.getSilverSending(locationId) - 10); }, [uiManager, locationId, updateSilverSending] ); useOnUp( '+', () => { updateSilverSending(uiManager.getSilverSending(locationId) + 10); }, [uiManager, locationId, updateSilverSending] ); const artifacts = usePlanetInactiveArtifacts(p, uiManager); const spaceshipsYouOwn = artifacts.filter( (a) => isSpaceShip(a.artifactType) && a.controller === account ); let abandonRow; if (p.value && p.value.transactions?.hasTransaction(isUnconfirmedReleaseTx)) { abandonRow = ( ); } else if (p.value && !p.value.destroyed) { abandonRow = ( ); } let sendRow; if (p.value && p.value.transactions?.hasTransaction(isUnconfirmedMoveTx)) { sendRow = ( ); } else { const isDisabled = (p.value?.destroyed || !owned) && !isSendingShip; sendRow = ( ); } return ( {owned && !p.value?.destroyed && ( <> {p.value && p.value.silver > 0 && ( )} )} {p.value && artifacts.length > 0 && ( )} {spaceshipsYouOwn.length > 0 || owned ? sendRow : null} {uiManager.getSpaceJunkEnabled() && owned ? abandonRow : null} ); } ================================================ FILE: src/Frontend/Views/Share.tsx ================================================ import { EthConnection } from '@darkforest_eth/network'; import React, { ReactNode, useEffect, useRef, useState } from 'react'; import styled, { css } from 'styled-components'; import { Account, getAccounts } from '../../Backend/Network/AccountManager'; import { getEthConnection } from '../../Backend/Network/Blockchain'; import ReaderDataStore from '../../Backend/Storage/ReaderDataStore'; import LandingPageCanvas from '../Renderers/LandingPageCanvas'; import dfstyles from '../Styles/dfstyles'; import { useUIManager } from '../Utils/AppHooks'; import { TerminalHandle } from './Terminal'; const ShareWrapper = styled.div` width: 100%; height: 100%; & p { margin: 0.5em 0; & a { color: ${dfstyles.colors.dfblue}; &:hover { text-decoration: underline; } } } `; const OnTop = styled.div` width: 100%; height: 100%; position: absolute; z-index: 2; `; const AddressChooserContainer = styled.div` width: 500px; background-color: ${dfstyles.colors.text}; color: black; padding: 8px; margin: 4px; `; const AddressOption = styled.div` ${({ selected }: { selected: boolean }) => css` background-color: ${dfstyles.colors.text}; cursor: pointer; display: block; margin: 3px; font-weight: ${selected ? 'bold' : 'unset'}; &:hover { text-decoration: underline; } `} `; export interface ShareProps { load: (store: ReaderDataStore) => Promise; children: (state: T | undefined, loading: boolean, error: Error | undefined) => ReactNode; } /** * Helper component that allows you to load data from the contract, as if it was * viewed from a particular account. Allows you to switch accounts. Just pass in: * * 1) a function that loads the data you want, given a [[ReaderDataStore]] * 2) a function that renders the given data with React * * ... and this component will take care of loading what you want. */ export function Share(props: ShareProps) { const terminalHandle = useRef(); const [ethConnection, setEthConnection] = useState(); const knownAccounts = [undefined, ...getAccounts()]; const [currentAccount, setCurrentAccount] = useState(knownAccounts[0]); const [store, setStore] = useState(); const [state, setState] = useState(); const [error, setError] = useState(); const [loading, setLoading] = useState(false); const uiManager = useUIManager(); const contractAddress = uiManager.getContractAddress(); const selectAccount = (idx: number) => () => { setCurrentAccount(knownAccounts[idx]); }; useEffect(() => { getEthConnection() .then((ethConnection) => { setEthConnection(ethConnection); }) .catch(console.error); }, []); useEffect(() => { if ( terminalHandle.current && !loading && (!store || store?.getViewer() !== currentAccount) && ethConnection ) { store?.destroy(); setStore(undefined); setLoading(true); const config = { connection: ethConnection, viewer: currentAccount?.address, contractAddress, }; ReaderDataStore.create(config).then(async (store) => { setStore(store); try { setState(await props.load(store)); } catch (e) { setError(e); } setLoading(false); }); } }, [store, currentAccount, loading, ethConnection, props, contractAddress]); return (

view as...

{knownAccounts.map((addr, i) => ( {addr || 'anonymous'} ))}
{props.children(state, loading, error)}
); } ================================================ FILE: src/Frontend/Views/SidebarPane.tsx ================================================ import { ModalName } from '@darkforest_eth/types'; import React, { useState } from 'react'; import styled from 'styled-components'; import { Hook } from '../../_types/global/GlobalTypes'; import { BorderlessPane, EmSpacer } from '../Components/CoreUI'; import { DFZIndex } from '../Utils/constants'; import { TOGGLE_HELP_PANE, TOGGLE_PLUGINS_PANE, TOGGLE_SETTINGS_PANE, TOGGLE_TRANSACTIONS_PANE, TOGGLE_YOUR_ARTIFACTS_PANE, TOGGLE_YOUR_PLANETS_DEX_PANE, } from '../Utils/ShortcutConstants'; import { ModalToggleButton } from './ModalIcon'; export function SidebarPane({ settingsHook, helpHook, pluginsHook, yourArtifactsHook, planetdexHook, transactionLogHook, }: { settingsHook: Hook; helpHook: Hook; pluginsHook: Hook; yourArtifactsHook: Hook; planetdexHook: Hook; transactionLogHook: Hook; }) { const [sidebarHovered, setSidebarHovered] = useState(false); return ( setSidebarHovered(true)} onMouseLeave={() => setSidebarHovered(false)} > ); } const WindowTogglesPaneContainer = styled.div` display: flex; justify-content: center; align-items: center; height: 100vh; position: absolute; top: 0; left: 0; `; ================================================ FILE: src/Frontend/Views/SortableTable.tsx ================================================ import React, { useCallback, useState } from 'react'; import styled, { css } from 'styled-components'; import dfstyles from '../Styles/dfstyles'; import { Table } from './Table'; const TableCell = styled.div` padding: 2px 4px; `; const Header = styled(TableCell)` ${({ isActive, isReverse }: { isActive: boolean; isReverse: boolean }) => css` padding: 2px 4px; font-weight: normal; color: ${dfstyles.colors.text}; user-select: none; cursor: pointer; ${isActive && 'text-decoration: underline;'} ${isActive && 'font-weight: bold;'} ${isReverse && 'transform: scaleY(-1);'} &:hover { text-decoration: underline; } `} `; export function SortableTable({ rows, headers, columns, sortFunctions, alignments, paginated, }: { rows: T[]; headers: React.ReactNode[]; columns: Array<(t: T, i: number) => React.ReactNode>; sortFunctions: Array<(left: T, right: T) => number>; alignments?: Array<'r' | 'c' | 'l'>; paginated?: boolean; }) { const [sortByColumn, setSortByColumn] = useState(undefined); const [reverse, setReverse] = useState(false); const sortFn = sortByColumn !== undefined ? sortFunctions[sortByColumn] : undefined; const sortedRows = [...rows]; if (sortFn !== undefined) { sortedRows.sort((a, b) => { if (reverse) { return sortFn(b, a); } else { return sortFn(a, b); } }); } // when you click on a column, cycle between three states: // 1) sort by that column // 2) sort by the reverse of that column // 3) sort by nothing const onColumnTitleClicked = useCallback( (columnIndex: number) => { if (sortByColumn === columnIndex) { if (reverse) { setReverse(false); setSortByColumn(undefined); } else { setReverse(true); } } else { setSortByColumn(columnIndex); setReverse(false); } }, [sortByColumn, reverse] ); return (
round complete
players {leaderboard.entries.length}
ranked players {rankedPlayers.length}
(
onColumnTitleClicked(i)} isActive={sortByColumn === i} isReverse={reverse && sortByColumn === i} > {originalHeader}
))} columns={columns.map( (originalColumn) => function Column(t, i) { return {originalColumn(t, i)}; } )} alignments={alignments} /> ); } ================================================ FILE: src/Frontend/Views/TabbedView.tsx ================================================ import React, { useState } from 'react'; import styled, { css } from 'styled-components'; import { Spacer } from '../Components/CoreUI'; import dfstyles from '../Styles/dfstyles'; /** * This component allows you to render several tabs of content. Each tab can be selected for viewing * by clicking on its corresponding tab button. Useful for displaying lots of slightly different but * related information to the user. */ export function TabbedView({ tabTitles, tabContents, style, }: { tabTitles: string[]; tabContents: (tabIndex: number) => React.ReactNode; style?: React.CSSProperties; }) { const [selectedTabIndex, setSelectedTabIndex] = useState(0); return (
{tabTitles.map((title, i) => ( { setSelectedTabIndex(i); }} > {title} ))} {tabContents(selectedTabIndex)}
); } const TabButton = styled.div<{ active: boolean }>` ${({ active }: { active: boolean }) => css` color: ${dfstyles.colors.subtext}; text-decoration: underline; border-radius: 3px; border: 1px solid ${dfstyles.colors.borderDarkest}; padding: 4px 8px; margin-right: 4px; margin-left: 4px; flex-grow: 1; text-align: center; cursor: pointer; user-select: none; &:first-child { margin-left: 0; } &:last-child { margin-right: 0; } &:hover { color: ${dfstyles.colors.text}; background-color: ${dfstyles.colors.backgroundlighter}; ${active && css` color: ${dfstyles.colors.text}; background-color: ${dfstyles.colors.dfgreendark}; `} } ${active && css` cursor: default; color: ${dfstyles.colors.text}; background-color: ${dfstyles.colors.dfgreendark}; `} `} `; const TabButtonContainer = styled.div` display: flex; justify-content: center; align-items: center; flex-direction: row; `; ================================================ FILE: src/Frontend/Views/Table.tsx ================================================ import React, { CSSProperties, useState } from 'react'; import styled from 'styled-components'; import { Btn } from '../Components/Btn'; import { Spacer } from '../Components/CoreUI'; import dfstyles from '../Styles/dfstyles'; const TableElement = styled.table` width: 100%; overflow-y: scroll; scrollbar-width: initial; border-radius: ${dfstyles.borderRadius}; `; const ScrollableBody = styled.tbody` width: 100%; `; const AlignmentOptions: { [key: string]: CSSProperties['textAlign'] } = { r: 'right', l: 'left', c: 'center', }; const PaginationContainer = styled.div` width: 100%; display: flex; flex-direction: row; justify-content: space-around; `; /** * React api for creating tables. * @param rows - rows of an arbitrary type * @param headers - required (for now) array of strings that head each column * @param columns - functions, one per column, that convert a row into the react representation of * that row's column's value. * @param alignments - optional, one per column, specifies that the text-alignment in that cell is * either right, center, or left, represented by the characters 'r', 'c', and 'l' */ export function Table({ rows, headers, columns, alignments, headerStyle, paginated, }: { rows: T[]; headers: React.ReactNode[]; columns: Array<(t: T, i: number) => React.ReactNode>; alignments?: Array<'r' | 'c' | 'l'>; headerStyle?: React.CSSProperties; paginated?: boolean; }) { const itemsPerPage = 10; const [page, setPage] = useState(0); const visibleRows = paginated ? rows.slice(page * itemsPerPage, page * itemsPerPage + itemsPerPage) : rows; return ( <> {headers.map((h: string, colIdx: number) => ( ))} {visibleRows.map((row: T, rowIdx: number) => ( {columns.map((column, colIdx) => ( ))} ))} {paginated && rows.length > itemsPerPage && ( <>
setPage(Math.max(page - 1, 0))}>< Page: {page + 1} of {Math.floor(rows.length / itemsPerPage) + 1} setPage(Math.min(page + 1, Math.floor(rows.length / itemsPerPage)))} > >
)} ); } ================================================ FILE: src/Frontend/Views/Terminal.tsx ================================================ import EventEmitter from 'events'; import React, { useCallback, useEffect, useImperativeHandle, useRef, useState } from 'react'; import styled, { css } from 'styled-components'; import { Link } from '../Components/CoreUI'; import { MythicLabelText } from '../Components/Labels/MythicLabel'; import { LoadingSpinner } from '../Components/LoadingSpinner'; import { Blue, Green, Invisible, Red, Sub, Subber, Text, White } from '../Components/Text'; import { LoadingBarHandle, TextLoadingBar } from '../Components/TextLoadingBar'; import dfstyles from '../Styles/dfstyles'; import { isFirefox } from '../Utils/BrowserChecks'; import { TerminalTextStyle } from '../Utils/TerminalTypes'; const ENTER_KEY_CODE = 13; const UP_ARROW_KEY_CODE = 38; const ON_INPUT = 'ON_INPUT'; export interface TerminalHandle { printElement: (element: React.ReactElement) => void; printLoadingBar: (prettyEntityName: string, ref: React.RefObject) => void; printLoadingSpinner: () => void; print: (str: string, style?: TerminalTextStyle) => void; println: (str: string, style?: TerminalTextStyle) => void; printShellLn: (str: string) => void; printLink: (str: string, onClick: () => void, style: TerminalTextStyle) => void; focus: () => void; removeLast: (n: number) => void; getInput: () => Promise; newline: () => void; setUserInputEnabled: (enabled: boolean) => void; setInput: (input: string) => void; clear: () => void; } export interface TerminalProps { promptCharacter: string; } export const Terminal = React.forwardRef(TerminalImpl); let terminalLineKey = 0; function TerminalImpl({ promptCharacter }: TerminalProps, ref: React.Ref) { const containerRef = useRef(document.createElement('div')); const inputRef = useRef(document.createElement('textarea')); const heightMeasureRef = useRef(document.createElement('textarea')); const [onInputEmitter] = useState(new EventEmitter()); const [fragments, setFragments] = useState([]); const [userInputEnabled, setUserInputEnabled] = useState(false); const [inputText, setInputText] = useState(''); const [inputHeight, setInputHeight] = useState(1); const [previousInput, setPreviousInput] = useState(''); const append = useCallback( (node: React.ReactNode) => { setFragments((lines) => { return [...lines.slice(-199), {node}]; }); }, [setFragments] ); const removeLast = useCallback( (n: number) => { setFragments((lines) => { return [...lines.slice(0, lines.length - n)]; }); }, [setFragments] ); const newline = useCallback(() => { append(
); }, [append]); const print = useCallback( (str: string, style = TerminalTextStyle.Sub, onClick: (() => void) | undefined = undefined) => { let fragment: JSX.Element; let innerFragment: JSX.Element = {str}; if (onClick !== undefined) { innerFragment = {innerFragment}; } switch (style) { case TerminalTextStyle.Mythic: fragment = ; break; case TerminalTextStyle.Green: fragment = {innerFragment}; break; case TerminalTextStyle.Blue: fragment = {innerFragment}; break; case TerminalTextStyle.Sub: fragment = {innerFragment}; break; case TerminalTextStyle.Subber: fragment = {innerFragment}; break; case TerminalTextStyle.Text: fragment = {innerFragment}; break; case TerminalTextStyle.White: fragment = {innerFragment}; break; case TerminalTextStyle.Red: fragment = {innerFragment}; break; case TerminalTextStyle.Invisible: fragment = {innerFragment}; break; case TerminalTextStyle.Underline: fragment = ( {innerFragment} ); break; default: fragment = {innerFragment}; } append(fragment); }, [append] ); const onKeyUp = async (e: React.KeyboardEvent) => { e.stopPropagation(); if (e.keyCode === ENTER_KEY_CODE && !e.shiftKey) { e.preventDefault(); print(promptCharacter + ' ', TerminalTextStyle.Green); print(inputText, TerminalTextStyle.Text); newline(); onInputEmitter.emit(ON_INPUT, inputText); setPreviousInput(inputText); setInputHeight(1); setInputText(''); } else if (e.keyCode === UP_ARROW_KEY_CODE && inputText === '' && previousInput !== '') { setInputHeight(1); setInputText(previousInput); } }; const preventEnterDefault = (e: React.KeyboardEvent): void => { e.stopPropagation(); if (e.keyCode === ENTER_KEY_CODE && !e.shiftKey) { e.preventDefault(); } }; useEffect(() => { if (userInputEnabled) { inputRef.current.focus(); } }, [userInputEnabled]); const scrollToEnd = () => { containerRef.current.scrollTo(0, containerRef.current.scrollHeight); }; useEffect(() => { scrollToEnd(); }, [fragments]); useEffect(() => { setInputHeight(heightMeasureRef.current.scrollHeight); }, [inputText]); useImperativeHandle( ref, () => ({ printElement: (element: React.ReactElement) => { append(element); }, printLoadingBar: (prettyEntityName: string, ref: React.RefObject) => { append(); }, print: (str: string, style?: TerminalTextStyle) => { print(str, style, undefined); }, println: (str: string, style?: TerminalTextStyle) => { print(str, style, undefined); newline(); }, printLink: (str: string, onClick: () => void, style: TerminalTextStyle) => { print(str, style, onClick); }, getInput: async () => { setUserInputEnabled(true); const text = await new Promise((resolve) => { onInputEmitter.once(ON_INPUT, (text: string) => resolve(text.trim())); }); setUserInputEnabled(false); return text; }, printShellLn: (text: string) => { print(promptCharacter + ' ', TerminalTextStyle.Green); print(text, TerminalTextStyle.Text); newline(); }, printLoadingSpinner: () => { append(); newline(); }, setInput: (input: string) => { if (inputRef.current) { setInputText(input); } }, focus: () => { inputRef.current?.focus(); }, newline, removeLast, setUserInputEnabled, clear: () => { setFragments([]); }, }), [onInputEmitter, promptCharacter, newline, print, append, removeLast, setFragments] ); return ( {fragments} { if (userInputEnabled) inputRef.current.focus(); }} > {promptCharacter + ' '} {} : preventEnterDefault} value={inputText} onChange={(e) => { if (userInputEnabled) { setInputText(e.target.value); } }} /> {/* "ghost" textarea used to measure the scrollHeight of the input */} {}} value={inputText} /> ); } const Prompt = styled.span` ${({ userInputEnabled }: { userInputEnabled: boolean }) => css` display: flex; justify-content: flex-start; flex-direction: row; opacity: ${userInputEnabled ? 1 : 0}; `} `; const TextAreas = styled.div` display: flex; flex-direction: column; justify-content: flex-start; width: 100%; `; const InputTextArea = styled.textarea` ${({ height }: { height: number }) => css` background: none; outline: none; border: none; color: ${dfstyles.colors.text}; height: ${height}px; resize: none; flex-grow: ${height === 0 ? 0 : 1}; `} `; const TerminalContainer = styled.div` height: 100%; width: 100%; margin: 0 auto; overflow: scroll; white-space: pre-wrap; overflow-wrap: break-word; & span { word-break: break-all; } @media (max-width: ${dfstyles.screenSizeS}) { font-size: ${dfstyles.fontSizeXS}; } `; ================================================ FILE: src/Frontend/Views/TopBar.tsx ================================================ import { Monomitter } from '@darkforest_eth/events'; import { weiToEth } from '@darkforest_eth/network'; import { EthAddress, ModalName, TooltipName } from '@darkforest_eth/types'; import React, { useEffect, useState } from 'react'; import styled from 'styled-components'; import { CaptureZonesGeneratedEvent } from '../../Backend/GameLogic/CaptureZoneGenerator'; import { Hook } from '../../_types/global/GlobalTypes'; import { AlignCenterHorizontally } from '../Components/CoreUI'; import { AccountLabel } from '../Components/Labels/Labels'; import { Gold, Red, Sub, Text, White } from '../Components/Text'; import { TooltipTrigger } from '../Panes/Tooltip'; import { usePlayer, useUIManager } from '../Utils/AppHooks'; import { DFZIndex } from '../Utils/constants'; import { useEmitterSubscribe, useEmitterValue } from '../Utils/EmitterHooks'; import { ModalToggleButton } from './ModalIcon'; import { NetworkHealth } from './NetworkHealth'; import { Paused } from './Paused'; const TopBarContainer = styled.div` z-index: ${DFZIndex.MenuBar}; padding: 0 2px; width: 530px; `; const Numbers = styled.div` display: inline-block; `; function BoardPlacement({ account }: { account: EthAddress | undefined }) { const uiManager = useUIManager(); const player = usePlayer(uiManager, account); let content; if (!player.value) { content = n/a; } else { let formattedScore = 'n/a'; if (player.value.score !== undefined && player.value.score !== null) { formattedScore = player.value.score.toLocaleString(); } content = ( score: {formattedScore} ); } return {content}; } function SpaceJunk({ account }: { account: EthAddress | undefined }) { const uiManager = useUIManager(); const [spaceJunk, setSpaceJunk] = useState(0); const [spaceJunkLimit, setSpaceJunkLimit] = useState(0); useEffect(() => { if (!uiManager) return; const gameManager = uiManager.getGameManager(); const refreshSpaceJunk = () => { if (!account) return; setSpaceJunk(gameManager.getPlayerSpaceJunk(account) || 0); setSpaceJunkLimit(gameManager.getPlayerSpaceJunkLimit(account) || 0); }; const sub = gameManager.playersUpdated$.subscribe(() => { refreshSpaceJunk(); }); refreshSpaceJunk(); return () => sub.unsubscribe(); }, [uiManager, account]); return ( space junk:{' '} {spaceJunk} / {spaceJunkLimit} ); } function CaptureZoneExplanation() { const uiManager = useUIManager(); const numberedItem = (n: number, content: string) => (
  • {n}.) {content}
  • ); return ( <> Capture Zones: Energy fluctations are creating highly valuable zones of space.{' '} Invading and holding planets in these areas give you score! The zones are marked as gold rings on your map.

    In order to capture a planet in a zone, you must:
      {numberedItem(1, 'Own a planet in the capture zone.')} {numberedItem(2, 'Start the invasion by clicking the Invade button.')} {numberedItem( 3, `Hold the planet for ${uiManager.contractConstants.CAPTURE_ZONE_HOLD_BLOCKS_REQUIRED} blocks.` )} {numberedItem( 4, 'Capture the planet by clicking the Capture button (Capturing does not require you to be in the zone, only Invading).' )}

    Planets can only be Captured once. However, after an Invasion has started, anyone can capture it. {' '} If you see an opponent start their Invasion, you can take the planet from them and Capture it for yourself! ); } function CaptureZones({ emitter, nextChangeBlock, }: { emitter: Monomitter; nextChangeBlock: number; }) { const uiManager = useUIManager(); const currentBlockNumber = useEmitterValue(uiManager.getEthConnection().blockNumber$, undefined); const [nextGenerationBlock, setNextGenerationBlock] = useState( Math.max( uiManager.contractConstants.GAME_START_BLOCK + uiManager.contractConstants.CAPTURE_ZONE_CHANGE_BLOCK_INTERVAL, nextChangeBlock ) ); useEmitterSubscribe( emitter, (zoneGeneration) => { setNextGenerationBlock(zoneGeneration.nextChangeBlock); }, [setNextGenerationBlock] ); return ( }> Capture Zones change in {nextGenerationBlock - (currentBlockNumber || 0)} blocks. ); } export function TopBar({ twitterVerifyHook }: { twitterVerifyHook: Hook }) { const uiManager = useUIManager(); const player = usePlayer(uiManager); const account = player.value?.address; const twitter = player.value?.twitter; const balance = useEmitterValue(uiManager.getMyBalance$(), uiManager.getMyBalanceBn()); let captureZones = null; if (uiManager.captureZonesEnabled) { const captureZoneGenerator = uiManager.getCaptureZoneGenerator(); if (captureZoneGenerator) { const emitter = captureZoneGenerator.generated$; const nextChangeBlock = captureZoneGenerator.getNextChangeBlock(); captureZones = ; } } return ( Your burner wallet address.} > Your burner wallet balance.} > ({weiToEth(balance).toFixed(2)} xDAI) {process.env.DF_WEBSERVER_URL && ( <> Connect your burner wallet to your twitter account.} > )} {captureZones} {uiManager.getSpaceJunkEnabled() && ( <> )} ); } ================================================ FILE: src/Frontend/Views/UpgradePreview.tsx ================================================ import { Planet, Upgrade, UpgradeBranchName } from '@darkforest_eth/types'; import React from 'react'; import styled from 'styled-components'; import { getPlanetMaxRank, getPlanetRank, upgradeName } from '../../Backend/Utils/Utils'; import { Icon, IconType } from '../Components/Icons'; import { Green, Red, Sub } from '../Components/Text'; import dfstyles from '../Styles/dfstyles'; const StyledUpgradePreview = styled.div` min-width: 15em; width: 100%; border-radius: 3px; border: 1px solid ${dfstyles.colors.borderDark}; font-size: ${dfstyles.fontSizeXS}; `; const StatRow = styled.div` display: flex; flex-direction: row; justify-content: center; align-items: center; padding-left: 4px; padding-right: 4px; & > span { margin-left: 0.2em; &:nth-child(1) { color: ${dfstyles.colors.subtext}; margin-left: 0; width: 9em; } &:nth-child(2), &:nth-child(4) { text-align: center; width: 6em; flex-grow: 1; } &:nth-child(3) { // arrow text-align: center; width: 1.5em; /* Set the Icon color to something a little dimmer */ --df-icon-color: ${dfstyles.colors.subtext}; } &:nth-child(5) { width: 5em; text-align: right; } } &.upgrade-willupdate { background: ${dfstyles.colors.backgroundlighter}; } `; const StatRowFilled = ({ planet, upgrade, title, stat, className, }: { planet: Planet | undefined; upgrade: Upgrade | undefined; title: string; stat: string; className?: string; }) => { const getStat = (stat: string): number => { if (!planet) return 0; // eslint-disable-next-line @typescript-eslint/no-explicit-any const mySelected = planet as any; if (stat === 'silverGrowth') return mySelected[stat] * 60; else return mySelected[stat]; }; const statNow = (stat: string): string => { const num = getStat(stat); if (num % 1.0 === 0) return num.toFixed(0); else return num.toFixed(2); }; const getStatFuture = (stat: string): number => { if (!planet) return 0; // eslint-disable-next-line @typescript-eslint/no-explicit-any const mySelected = planet as any; if (!upgrade) return mySelected[stat]; let mult = 1; if (stat === 'energyCap') { mult = upgrade.energyCapMultiplier / 100; } else if (stat === 'energyGrowth') { mult = upgrade.energyGroMultiplier / 100; } else if (stat === 'range') { mult = upgrade.rangeMultiplier / 100; } else if (stat === 'speed') { mult = upgrade.speedMultiplier / 100; } else if (stat === 'defense') { mult = upgrade.defMultiplier / 100; } return getStat(stat) * mult; }; const statFuture = (stat: string): string => { const num = getStatFuture(stat); if (num % 1.0 === 0) return num.toFixed(0); else return num.toFixed(2); }; const getStatDiff = (stat: string): number => { return getStatFuture(stat) - getStat(stat); }; const statDiff = (stat: string): React.ReactNode => { const diff: number = getStatDiff(stat); if (diff < 0) return {diff.toFixed(2)}; else if (diff > 0) return +{diff.toFixed(2)}; else return 0; }; const updateClass = (stat: string): string => { if (getStat(stat) !== getStatFuture(stat)) return 'upgrade-willupdate'; return ''; }; return ( {title} {statNow(stat)} {statFuture(stat)} {statDiff(stat)} ); }; export function UpgradePreview({ planet, upgrade, branchName, cantUpgrade, }: { planet: Planet | undefined; upgrade: Upgrade | undefined; branchName: UpgradeBranchName | undefined; cantUpgrade: boolean; }) { const branchStrName = branchName !== undefined && upgradeName(branchName); const maxRank = getPlanetMaxRank(planet); const maxBranchRank = Math.min(4, maxRank); const branchUpgradeState = (branchName && planet && planet.upgradeState[branchName]) || 0; if (cantUpgrade) { upgrade = { defMultiplier: 100, energyCapMultiplier: 100, energyGroMultiplier: 100, rangeMultiplier: 100, speedMultiplier: 100, }; } const increment = cantUpgrade ? 0 : 1; return ( {branchStrName} Rank {branchUpgradeState} {branchUpgradeState + increment} of {maxBranchRank} max {cantUpgrade ? 0 : +1} Planet Rank {getPlanetRank(planet)} {getPlanetRank(planet) + increment} of {maxRank} max {cantUpgrade ? 0 : +1} ); } ================================================ FILE: src/Frontend/Views/WithdrawSilver.tsx ================================================ import { isUnconfirmedWithdrawSilverTx } from '@darkforest_eth/serde'; import { Planet, PlanetType, TooltipName } from '@darkforest_eth/types'; import React, { useCallback, useMemo, useState } from 'react'; import styled from 'styled-components'; import { Wrapper } from '../../Backend/Utils/Wrapper'; import { Hook } from '../../_types/global/GlobalTypes'; import { Btn } from '../Components/Btn'; import { CenterBackgroundSubtext } from '../Components/CoreUI'; import { DarkForestNumberInput, NumberInput } from '../Components/Input'; import { LoadingSpinner } from '../Components/LoadingSpinner'; import { Row } from '../Components/Row'; import { Red } from '../Components/Text'; import { TooltipTrigger } from '../Panes/Tooltip'; import dfstyles from '../Styles/dfstyles'; import { useUIManager } from '../Utils/AppHooks'; const StyledSilverInput = styled.div` width: fit-content; display: inline-flex; flex-direction: row; align-items: center; `; const AllBtn = styled.div` color: ${dfstyles.colors.subtext}; font-size: ${dfstyles.fontSizeS}; &:hover { cursor: pointer; text-decoration: underline; } `; const InputWrapper = styled.div` width: 5em; margin-right: 0.5em; `; function SilverInput({ amt, setAmt, wrapper, }: { amt: number | undefined; setAmt: Hook[1]; wrapper: Wrapper; }) { const click = useCallback(() => { if (wrapper.value) setAmt(wrapper.value.silver); }, [wrapper, setAmt]); return ( ) => setAmt(e.target.value)} value={amt} /> all ); } const TextWrapper = styled.span` width: 120px; font-size: ${dfstyles.fontSizeXS}; text-align: center; `; export function WithdrawSilver({ wrapper }: { wrapper: Wrapper }) { const uiManager = useUIManager(); const [error, setError] = useState(false); const [amt, setAmt] = useState(0); const withdraw = useCallback( (silver: number | undefined) => { if (!wrapper.value) return; if (typeof silver !== 'number') { setError(true); } else { uiManager.withdrawSilver(wrapper.value.locationId, silver); } setAmt(0); }, [wrapper, uiManager] ); const withdrawing = useMemo( () => !!wrapper.value?.transactions?.hasTransaction(isUnconfirmedWithdrawSilverTx), [wrapper] ); const empty = useMemo(() => !!(wrapper.value && wrapper.value.silver < 1), [wrapper]); if (wrapper.value?.planetType === PlanetType.TRADING_POST) { return ( <> {error && ( Error with amount entered. )} withdraw(amt)} disabled={withdrawing || empty}> {withdrawing ? : 'Withdraw Silver'} ); } else { return ( Select a Spacetime Rip ); } } ================================================ FILE: src/_types/darkforest/api/ChunkStoreTypes.ts ================================================ import type { Abstract, LocationId, Rectangle } from '@darkforest_eth/types'; /** * one of "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ" */ export type BucketId = Abstract; /** * Don't worry about the values here. Never base code off the values here. PLEASE. */ export type ChunkId = Abstract; /** * Chunks represent map data in some rectangle. This type represents a chunk when it is at rest in * IndexedDB. The reason for this type's existence is that we want to reduce the amount of data we * store on the user's computer. Shorter names hopefully means less data. */ export interface PersistedChunk { x: number; // left y: number; // bottom s: number; // side length l: PersistedLocation[]; p: number; // approximate avg perlin value. used for rendering } /** * A location is a point sample of the universe. This type represents that point sample at rest when * it is stored in IndexedDB. */ export interface PersistedLocation { x: number; y: number; h: LocationId; p: number; // perlin b: number; // biomebase perlin } /** * Abstract interface shared between different types of chunk stores. Currently we have one that * writes to IndexedDB, and one that simply throws away the data. */ export interface ChunkStore { hasMinedChunk: (chunkFootprint: Rectangle) => boolean; } ================================================ FILE: src/_types/darkforest/api/ContractsAPITypes.ts ================================================ import { ArtifactPointValues, EthAddress, UpgradeBranches } from '@darkforest_eth/types'; import { BigNumber as EthersBN } from 'ethers'; export const enum ZKArgIdx { PROOF_A, PROOF_B, PROOF_C, DATA, } export const enum InitArgIdxs { LOCATION_ID, PERLIN, RADIUS, PLANETHASH_KEY, SPACETYPE_KEY, PERLIN_LENGTH_SCALE, PERLIN_MIRROR_X, PERLIN_MIRROR_Y, } export const enum MoveArgIdxs { FROM_ID, TO_ID, TO_PERLIN, TO_RADIUS, DIST_MAX, PLANETHASH_KEY, SPACETYPE_KEY, PERLIN_LENGTH_SCALE, PERLIN_MIRROR_X, PERLIN_MIRROR_Y, SHIPS_SENT, SILVER_SENT, ARTIFACT_SENT, } export const enum UpgradeArgIdxs { LOCATION_ID, UPGRADE_BRANCH, } export const enum ContractEvent { PlayerInitialized = 'PlayerInitialized', ArrivalQueued = 'ArrivalQueued', PlanetUpgraded = 'PlanetUpgraded', PlanetHatBought = 'PlanetHatBought', PlanetTransferred = 'PlanetTransferred', PlanetInvaded = 'PlanetInvaded', PlanetCaptured = 'PlanetCaptured', LocationRevealed = 'LocationRevealed', ArtifactFound = 'ArtifactFound', ArtifactDeposited = 'ArtifactDeposited', ArtifactWithdrawn = 'ArtifactWithdrawn', ArtifactActivated = 'ArtifactActivated', ArtifactDeactivated = 'ArtifactDeactivated', PlanetSilverWithdrawn = 'PlanetSilverWithdrawn', AdminOwnershipChanged = 'AdminOwnershipChanged', AdminGiveSpaceship = 'AdminGiveSpaceship', PauseStateChanged = 'PauseStateChanged', LobbyCreated = 'LobbyCreated', } export const enum ContractsAPIEvent { PlayerUpdate = 'PlayerUpdate', PlanetUpdate = 'PlanetUpdate', PauseStateChanged = 'PauseStateChanged', ArrivalQueued = 'ArrivalQueued', ArtifactUpdate = 'ArtifactUpdate', RadiusUpdated = 'RadiusUpdated', LocationRevealed = 'LocationRevealed', /** * The transaction has been queued for future execution. */ TxQueued = 'TxQueued', /** * The transaction has been removed from the queue and is * calculating arguments in preparation for submission. */ TxProcessing = 'TxProcessing', /** * The transaction is queued, but is prioritized for execution * above other queued transactions. */ TxPrioritized = 'TxPrioritized', /** * The transaction has been submitted and we are awaiting * confirmation. */ TxSubmitted = 'TxSubmitted', /** * The transaction has been confirmed. */ TxConfirmed = 'TxConfirmed', /** * The transaction has failed for some reason. This * could either be a revert or a purely client side * error. In the case of a revert, the transaction hash * will be included in the transaction object. */ TxErrored = 'TxErrored', /** * The transaction was cancelled before it left the queue. */ TxCancelled = 'TxCancelled', PlanetTransferred = 'PlanetTransferred', PlanetClaimed = 'PlanetClaimed', LobbyCreated = 'LobbyCreated', } // planet locationID(BigInt), branch number export type UpgradeArgs = [string, string]; export type MoveArgs = [ [string, string], // proofA [ // proofB [string, string], [string, string] ], [string, string], // proofC [ string, // from locationID (BigInt) string, // to locationID (BigInt) string, // perlin at to string, // radius at to string, // distMax string, // planetHashKey string, // spaceTypeKey string, // perlin lengthscale string, // perlin xmirror (1 true, 0 false) string, // perlin ymirror (1 true, 0 false) string, // ships sent string, // silver sent string, // artifactId sent string // is planet being released (1 true, 0 false) ] ]; // Same as reveal args with Explicit coords attached export type ClaimArgs = [ [string, string], [[string, string], [string, string]], [string, string], [string, string, string, string, string, string, string, string, string] ]; export type DepositArtifactArgs = [string, string]; // locationId, artifactId export type WithdrawArtifactArgs = [string, string]; // locationId, artifactId export type WhitelistArgs = [string, string]; // hashed whitelist key, recipient address export type PlanetTypeWeights = [number, number, number, number, number]; // relative frequencies of the 5 planet types export type PlanetTypeWeightsByLevel = [ PlanetTypeWeights, PlanetTypeWeights, PlanetTypeWeights, PlanetTypeWeights, PlanetTypeWeights, PlanetTypeWeights, PlanetTypeWeights, PlanetTypeWeights, PlanetTypeWeights, PlanetTypeWeights ]; export type PlanetTypeWeightsBySpaceType = [ PlanetTypeWeightsByLevel, PlanetTypeWeightsByLevel, PlanetTypeWeightsByLevel, PlanetTypeWeightsByLevel ]; export interface ContractConstants { ADMIN_CAN_ADD_PLANETS: boolean; WORLD_RADIUS_LOCKED: boolean; WORLD_RADIUS_MIN: number; DISABLE_ZK_CHECKS: boolean; PLANETHASH_KEY: number; SPACETYPE_KEY: number; BIOMEBASE_KEY: number; PERLIN_LENGTH_SCALE: number; PERLIN_MIRROR_X: boolean; PERLIN_MIRROR_Y: boolean; TOKEN_MINT_END_SECONDS: number; MAX_NATURAL_PLANET_LEVEL: number; TIME_FACTOR_HUNDREDTHS: number; /** * The perlin value at each coordinate determines the space type. There are four space * types, which means there are four ranges on the number line that correspond to * each space type. This function returns the boundary values between each of these * four ranges: `PERLIN_THRESHOLD_1`, `PERLIN_THRESHOLD_2`, `PERLIN_THRESHOLD_3`. */ PERLIN_THRESHOLD_1: number; PERLIN_THRESHOLD_2: number; PERLIN_THRESHOLD_3: number; INIT_PERLIN_MIN: number; INIT_PERLIN_MAX: number; SPAWN_RIM_AREA: number; BIOME_THRESHOLD_1: number; BIOME_THRESHOLD_2: number; PLANET_RARITY: number; /** The chance for a planet to be a specific level. Each index corresponds to a planet level (index 5 is level 5 planet). The lower the number the lower the chance. Note: This does not control if a planet spawns or not, just the level when it spawns. */ PLANET_LEVEL_THRESHOLDS: [ number, number, number, number, number, number, number, number, number, number ]; PLANET_TRANSFER_ENABLED: boolean; PLANET_TYPE_WEIGHTS: PlanetTypeWeightsBySpaceType; ARTIFACT_POINT_VALUES: ArtifactPointValues; /** * How much score silver gives when withdrawing. * Expressed as a percentage integer. * (100 is 100%) */ SILVER_SCORE_VALUE: number; // Space Junk SPACE_JUNK_ENABLED: boolean; /** Total amount of space junk a player can take on. This can be overridden at runtime by updating this value for a specific player in storage. */ SPACE_JUNK_LIMIT: number; /** The amount of junk that each level of planet gives the player when moving to it for the first time. */ PLANET_LEVEL_JUNK: [ number, number, number, number, number, number, number, number, number, number ]; /** The speed boost a movement receives when abandoning a planet. */ ABANDON_SPEED_CHANGE_PERCENT: number; /** The range boost a movement receives when abandoning a planet. */ ABANDON_RANGE_CHANGE_PERCENT: number; PHOTOID_ACTIVATION_DELAY: number; LOCATION_REVEAL_COOLDOWN: number; CLAIM_PLANET_COOLDOWN?: number; defaultPopulationCap: number[]; defaultPopulationGrowth: number[]; defaultSilverCap: number[]; defaultSilverGrowth: number[]; defaultRange: number[]; defaultSpeed: number[]; defaultDefense: number[]; defaultBarbarianPercentage: number[]; planetLevelThresholds: number[]; planetCumulativeRarities: number[]; upgrades: UpgradeBranches; adminAddress: EthAddress; // Capture Zones GAME_START_BLOCK: number; CAPTURE_ZONES_ENABLED: boolean; CAPTURE_ZONE_COUNT: number; CAPTURE_ZONE_CHANGE_BLOCK_INTERVAL: number; CAPTURE_ZONE_RADIUS: number; CAPTURE_ZONE_PLANET_LEVEL_SCORE: [ number, number, number, number, number, number, number, number, number, number ]; CAPTURE_ZONE_HOLD_BLOCKS_REQUIRED: number; CAPTURE_ZONES_PER_5000_WORLD_RADIUS: number; } export type ClientMockchainData = | null | undefined | number | string | boolean | EthersBN | ClientMockchainData[] | { [key in string | number]: ClientMockchainData; }; export const enum PlanetEventType { ARRIVAL, } ================================================ FILE: src/_types/darkforest/api/UtilityServerAPITypes.ts ================================================ export type AddressTwitterMap = { [ethAddress: string]: string; }; ================================================ FILE: src/_types/file-loader/FileWorkerTypes.ts ================================================ declare module '@darkforest_eth/snarks/*.wasm' { const path: string; export default path; } declare module '@darkforest_eth/snarks/*.zkey' { const path: string; export default path; } declare module '@darkforest_eth/contracts/abis/*.json' { const path: string; export default path; } ================================================ FILE: src/_types/global/GlobalTypes.ts ================================================ import { Rectangle } from '@darkforest_eth/types'; import { Dispatch, SetStateAction } from 'react'; import GameManager from '../../Backend/GameLogic/GameManager'; import GameUIManager from '../../Backend/GameLogic/GameUIManager'; export type Hook = [T, Dispatch>]; declare global { interface Window { /* eslint-disable-next-line @typescript-eslint/no-explicit-any */ snarkjs: any; // TODO: these three should eventually live in some sort of `DFTerminal` namespace // instead of global df?: GameManager; ui?: GameUIManager; // injected into global scope via netlify snippets - this is a permalink // to the deployment hosted on netlify. DEPLOY_URL?: string; } } export type HashConfig = { planetHashKey: number; spaceTypeKey: number; biomebaseKey: number; perlinLengthScale: number; // power of two up to 8192 perlinMirrorX: boolean; perlinMirrorY: boolean; planetRarity: number; // only for fakeHash (DISABLE_ZK_CHECKS on) }; export const enum StatIdx { EnergyCap = 0, EnergyGro = 1, Range = 2, Speed = 3, Defense = 4, SpaceJunk = 5, } export interface MinerWorkerMessage { chunkFootprint: Rectangle; workerIndex: number; totalWorkers: number; planetRarity: number; jobId: number; useMockHash: boolean; planetHashKey: number; spaceTypeKey: number; biomebaseKey: number; perlinLengthScale: number; perlinMirrorX: boolean; perlinMirrorY: boolean; } // info about when the player can next reveal coordinates export interface RevealCountdownInfo { myLastRevealTimestamp?: number; // if undefined, never revealed before currentlyRevealing: boolean; // true iff player has an unconfirmedReveal currently being processed revealCooldownTime: number; // in seconds } export interface ClaimCountdownInfo { myLastClaimTimestamp?: number; // if undefined, never revealed before currentlyClaiming: boolean; // true iff player has an unconfirmedReveal currently being processed claimCooldownTime: number; // in seconds } ================================================ FILE: src/_types/global/global.d.ts ================================================ /** * This file declares globals that are available to all plugins. */ import GameManager from '../../Backend/GameLogic/GameManager'; import GameUIManager from '../../Backend/GameLogic/GameUIManager'; declare global { const df: GameManager; const ui: GameUIManager; // TODO: Figure out a way to share this /** * All plugins must conform to this interface. Provides facilities for * displaying an interactive UI, as well as references to game state, * which are set externally. */ interface DFPlugin { /** * If present, called once when the user clicks 'run' in the plugin * manager modal. */ render?: (div: HTMLDivElement) => Promise; /** * If present, called at the same framerate the the game is running at, * and allows you to draw on top of the game UI. */ draw?: (ctx: CanvasRenderingContext2D) => void; /** * Called when the plugin is unloaded. Plugins unload whenever the * plugin is edited (modified and saved, or deleted). */ destroy?: () => void; } } ================================================ FILE: tsconfig.decs.json ================================================ // This file exists because the base tsconfig uses `noEmit`, but we still // want to generate the declaration files upon mirroring the project { "extends": "./tsconfig.json", "include": ["src/**/*"], "compilerOptions": { "rootDir": "src", "noEmit": false, "emitDeclarationOnly": true, "declaration": true, "declarationDir": "./declarations" } } ================================================ FILE: tsconfig.json ================================================ { "include": [ "src/**/*.ts", "src/**/*.tsx", "embedded_plugins/**/*.ts", "embedded_plugins/**/*.tsx", "plugins/**/*.ts", "plugins/**/*.tsx" ], "compilerOptions": { "module": "esnext", "target": "es2020", "sourceMap": true, "noImplicitAny": true, "strictNullChecks": true, "allowSyntheticDefaultImports": true, "jsx": "react", // use typescript to transpile jsx to js "allowJs": true, // allow a partial TypeScript and JavaScript codebase "moduleResolution": "Node", "lib": ["es2020", "dom"], "skipLibCheck": true, "noEmit": true }, "typedocOptions": { "entryPointStrategy": "expand", "entryPoints": ["src"], "out": "docs", "hideBreadcrumbs": true, "readme": "none", "disableSources": true, "cleanOutputDir": false, "githubPages": false, "excludeExternals": true, "listInvalidSymbolLinks": true } } ================================================ FILE: tsconfig.ref.json ================================================ { "extends": "./tsconfig.json", "references": [ { "path": "../packages/constants/tsconfig.ref.json" }, { "path": "../packages/contracts/tsconfig.ref.json" }, { "path": "../packages/events/tsconfig.ref.json" }, { "path": "../packages/gamelogic/tsconfig.ref.json" }, { "path": "../packages/hashing/tsconfig.ref.json" }, { "path": "../packages/hexgen/tsconfig.ref.json" }, { "path": "../packages/network/tsconfig.ref.json" }, { "path": "../packages/procedural/tsconfig.ref.json" }, { "path": "../packages/renderer/tsconfig.ref.json" }, { "path": "../packages/serde/tsconfig.ref.json" }, { "path": "../packages/settings/tsconfig.ref.json" }, { "path": "../packages/snarks/tsconfig.ref.json" }, { "path": "../packages/types/tsconfig.ref.json" }, { "path": "../packages/ui/tsconfig.ref.json" }, { "path": "../packages/whitelist/tsconfig.ref.json" } ], "compilerOptions": { "composite": true, "declarationMap": true } } ================================================ FILE: webpack.config.js ================================================ const path = require('path'); const dotenv = require('dotenv'); dotenv.config(); const resolvePackage = require('resolve-package-path'); const { EnvironmentPlugin } = require('webpack'); const HtmlWebpackPlugin = require('html-webpack-plugin'); const CopyPlugin = require('copy-webpack-plugin'); const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin'); // This code is used to lookup where the `@darkforest_eth` packages exist in the tree // whether they are in a monorepo or installed as packages function findScopeDirectory() { // Just chose the most likely package to be here, it could really be anything const pkg = '@darkforest_eth/contracts'; const contractsPackageJson = resolvePackage(pkg, __dirname); if (!contractsPackageJson) { throw new Error(`Unable to find the @darkforest_eth scope. Exiting...`); } const contractsDirectory = path.dirname(contractsPackageJson); const scopeDirectory = path.dirname(contractsDirectory); return scopeDirectory; } module.exports = { mode: 'production', entry: ['./src/Frontend/EntryPoints/index.tsx'], output: { path: path.join(__dirname, '/dist'), filename: 'bundle-[contenthash].min.js', publicPath: '/', clean: true, }, // Enable sourcemaps for debugging webpack's output. devtool: 'source-map', devServer: { port: 8081, historyApiFallback: true, }, resolve: { // Add '.ts' and '.tsx' as resolvable extensions. extensions: ['.ts', '.tsx', '...'], // Adding an alias for the `@darkforest_eth` packages, whether in a monorepo or packages alias: { '@darkforest_eth': findScopeDirectory(), }, }, module: { rules: [ // Still depends on raw-loader here, with the javascript/auto content type, // because otherwise the module can't be imported in PluginManager { test: /\.[jt]sx?$/, include: [path.join(__dirname, './embedded_plugins/')], type: 'javascript/auto', use: ['raw-loader', 'babel-loader'], }, { test: /\.ts(x?)$/, include: [path.join(__dirname, './src/')], use: ['babel-loader'], }, { test: /\.css$/, use: ['style-loader', 'css-loader'], }, { test: /\.(woff(2)?|ttf|eot|svg)$/, include: [path.join(__dirname, './src/')], type: 'asset/resource', generator: { filename: 'fonts/[name][ext]', }, }, // Any wasm, zkye, or json files from other packages should be loaded as a plain file { test: /\.(wasm|zkey|json)$/, type: 'asset/resource', }, // All output '.js' files will have any sourcemaps re-processed by 'source-map-loader' { enforce: 'pre', test: /\.js$/, loader: 'source-map-loader', options: { filterSourceMappingUrl(url, resourcePath) { // The sourcemaps in react-sortable are screwed up if (resourcePath.includes('react-sortablejs')) { return false; } return true; }, }, }, ], }, plugins: [ // We use ForkTsChecker plugin to run typechecking on `src/` // in the background and report errors into the frontent UI new ForkTsCheckerWebpackPlugin({ typescript: { diagnosticOptions: { semantic: true, syntactic: true, }, mode: 'readonly', }, }), // The string values are fallbacks if the env variable is not set new EnvironmentPlugin({ NODE_ENV: 'development', DEFAULT_RPC: 'https://rpc-df.xdaichain.com/', // This must be null to indicate to webpack that this environment variable is optional DF_WEBSERVER_URL: null, }), new HtmlWebpackPlugin({ template: './index.html', }), new CopyPlugin({ patterns: [{ from: 'public', to: 'public' }], }), ], };
    {h}
    {column(row, rowIdx)}