[ComponentEditorProps(category: "GameScripted/Gadgets", description: "Vanilla map gadget with a world-visible rendered surface")] class ERRM_SharedMapComponentClass : SCR_MapGadgetComponentClass { [Attribute("{255E1265F702B4AE}UI/layouts/ERRMap/SharedMapRenderTarget.layout", UIWidgets.ResourcePickerThumbnail, "Off-screen map render target", "layout")] protected ResourceName m_sLayoutName; [Attribute("0.12", UIWidgets.EditBox, "Initial map zoom in pixels per metre", "0.01 20", category: "Shared Map")] protected float m_fInitialZoom; ResourceName GetSharedMapLayout() { return m_sLayoutName; } float GetInitialZoom() { return m_fInitialZoom; } } // This remains a real SCR_MapGadgetComponent, so the normal M-key map, // animations, ruler and faction-specific protractor settings keep working. // The extra off-screen widget supplies the texture used by the held model. class ERRM_SharedMapComponent : SCR_MapGadgetComponent { protected const ResourceName MAP_CONFIG = "{A786DD4868598F15}Configs/Map/MapPlain.conf"; protected const int MAP_SETUP_DELAY_MS = 100; protected const int MAP_CAPTURE_DELAY_MS = 500; protected const int MAP_RETRY_DELAY_MS = 250; protected Widget m_ESMRootWidget; protected RTTextureWidget m_ESMRenderTarget; protected SCR_MapEntity m_ESMMapEntity; protected IEntity m_ESMCharacterOwner; protected bool m_bESMInHand; protected bool m_bESMOwnsMapRenderer; protected override void EOnInit(IEntity owner) { super.EOnInit(owner); ERRM_SharedMapComponentClass data = ERRM_SharedMapComponentClass.Cast(GetComponentData(owner)); WorkspaceWidget workspace = GetGame().GetWorkspace(); if (!data || !workspace || data.GetSharedMapLayout().IsEmpty()) return; m_ESMRootWidget = workspace.CreateWidgets(data.GetSharedMapLayout()); if (!m_ESMRootWidget) return; m_ESMRenderTarget = RTTextureWidget.Cast(m_ESMRootWidget.FindAnyWidget("RTTexture0")); if (!m_ESMRenderTarget) return; m_ESMRenderTarget.SetRenderTarget(owner); m_ESMRenderTarget.SetEnabled(false); } protected override void ModeSwitch(EGadgetMode mode, IEntity charOwner) { if (mode != EGadgetMode.IN_HAND || !m_ESMRootWidget || !m_ESMRenderTarget) { super.ModeSwitch(mode, charOwner); return; } m_bESMInHand = true; m_ESMCharacterOwner = charOwner; TryCaptureMapSurface(); } protected override void ModeClear(EGadgetMode mode) { if (mode == EGadgetMode.IN_HAND) StopMapCapture(); super.ModeClear(mode); } protected void TryCaptureMapSurface() { if (!m_bESMInHand) return; if (!m_ESMRootWidget || !m_ESMRenderTarget) { CompleteMapModeSwitch(); return; } m_ESMMapEntity = SCR_MapEntity.GetMapInstance(); if (!m_ESMMapEntity) { CompleteMapModeSwitch(); return; } // The owner's normal map has priority. Capture only after it closes; // otherwise this auxiliary render would replace or close the M-key UI. if (m_ESMMapEntity.IsOpen()) { GetGame().GetCallqueue().CallLater(TryCaptureMapSurface, MAP_RETRY_DELAY_MS, false); return; } m_ESMRenderTarget.SetEnabled(true); MapConfiguration mapConfig = m_ESMMapEntity.SetupMapConfig(EMapEntityMode.PLAIN, MAP_CONFIG, m_ESMRootWidget); if (!mapConfig) { CompleteMapModeSwitch(); return; } m_ESMMapEntity.OpenMap(mapConfig); m_bESMOwnsMapRenderer = true; RestoreCharacterCamera(); GetGame().GetCallqueue().CallLater(CenterMapOnHolder, MAP_SETUP_DELAY_MS, false); GetGame().GetCallqueue().CallLater(FinishMapCapture, MAP_CAPTURE_DELAY_MS, false); } protected void CenterMapOnHolder() { if (!m_bESMOwnsMapRenderer || !m_ESMMapEntity || !m_ESMCharacterOwner) return; ERRM_SharedMapComponentClass data = ERRM_SharedMapComponentClass.Cast(GetComponentData(GetOwner())); if (!data) return; vector holderPosition = m_ESMCharacterOwner.GetOrigin(); m_ESMMapEntity.ZoomPanSmooth(data.GetInitialZoom(), holderPosition[0], holderPosition[2], 0.1); } protected void FinishMapCapture() { if (!m_bESMOwnsMapRenderer) return; // Freeze the last rendered frame before closing the auxiliary map. // Leaving the widget enabled would redraw the closed map as an empty // texture and make the physical map appear to vanish. if (m_ESMRenderTarget) m_ESMRenderTarget.SetEnabled(false); if (m_ESMMapEntity && m_ESMMapEntity.IsOpen()) m_ESMMapEntity.CloseMap(); m_bESMOwnsMapRenderer = false; RestoreCharacterCamera(); CompleteMapModeSwitch(); } protected void CompleteMapModeSwitch() { if (!m_bESMInHand || !m_ESMCharacterOwner) return; // Start the stock map only after the world-visible surface owns a frame. super.ModeSwitch(EGadgetMode.IN_HAND, m_ESMCharacterOwner); } protected void StopMapCapture() { m_bESMInHand = false; GetGame().GetCallqueue().Remove(TryCaptureMapSurface); GetGame().GetCallqueue().Remove(CenterMapOnHolder); GetGame().GetCallqueue().Remove(FinishMapCapture); if (m_bESMOwnsMapRenderer && m_ESMMapEntity && m_ESMMapEntity.IsOpen()) m_ESMMapEntity.CloseMap(); m_bESMOwnsMapRenderer = false; m_ESMCharacterOwner = null; RestoreCharacterCamera(); } protected void RestoreCharacterCamera() { if (!GetGame()) return; PlayerController playerController = GetGame().GetPlayerController(); if (playerController) playerController.SetCharacterCameraRenderActive(true); } protected override void OnDelete(IEntity owner) { StopMapCapture(); // The entity may already be destroyed when this callback runs during a // world restart. Destroying the widget releases its render binding without // passing the deleted entity back into the native renderer. if (m_ESMRootWidget) m_ESMRootWidget.RemoveFromHierarchy(); m_ESMRenderTarget = null; m_ESMRootWidget = null; super.OnDelete(owner); } }