diff --git a/addons/core/Configs/System/chimeraMenus.conf b/addons/core/Configs/System/chimeraMenus.conf new file mode 100644 index 00000000..5b745770 --- /dev/null +++ b/addons/core/Configs/System/chimeraMenus.conf @@ -0,0 +1,8 @@ +MenuManager { + MenuPresets { + MenuPreset ACE_InspectGadgetMenu { + Layout "{6661B07C2C6D0CC7}UI/layouts/Gagets/ACE_InspectGadgetMenu.layout" + Class "ACE_InspectGadgetMenu" + } + } +} \ No newline at end of file diff --git a/addons/core/Configs/System/chimeraMenus.conf.meta b/addons/core/Configs/System/chimeraMenus.conf.meta new file mode 100644 index 00000000..0c100211 --- /dev/null +++ b/addons/core/Configs/System/chimeraMenus.conf.meta @@ -0,0 +1,17 @@ +MetaFileClass { + Name "{C747AFB6B750CE9A}Configs/System/chimeraMenus.conf" + Configurations { + CONFResourceClass PC { + } + CONFResourceClass XBOX_ONE : PC { + } + CONFResourceClass XBOX_SERIES : PC { + } + CONFResourceClass PS4 : PC { + } + CONFResourceClass PS5 : PC { + } + CONFResourceClass HEADLESS : PC { + } + } +} \ No newline at end of file diff --git a/addons/core/UI/layouts/Gagets/ACE_GamepadPhysicalButton.layout b/addons/core/UI/layouts/Gagets/ACE_GamepadPhysicalButton.layout new file mode 100644 index 00000000..45160dac --- /dev/null +++ b/addons/core/UI/layouts/Gagets/ACE_GamepadPhysicalButton.layout @@ -0,0 +1,30 @@ +ButtonWidgetClass { + Name "Button" + Slot FrameWidgetSlot "{6563682E29BADC1C}" { + OffsetLeft 0 + OffsetTop 0 + SizeX 128 + OffsetRight -128 + SizeY 128 + OffsetBottom -128 + Alignment 0.5 0.5 + SizeToContent 1 + } + Opacity 0 + components { + ACE_GamepadPhysicalButtonUIComponent "{65641D94B3955D9B}" { + } + } + style blank + { + ImageWidgetClass "{6563682E0ED041D0}" { + Name "GamepadCursorFocus" + Slot ButtonWidgetSlot "{65641D97992C7EAE}" { + } + Color 0.7605 0.3916 0.0802 1 + Texture "{272DB16BAE0E7D3C}UI/Textures/Editor/Cursors/GamepadCursor_Default.edds" + Image "" + Size 128 128 + } + } +} \ No newline at end of file diff --git a/addons/core/UI/layouts/Gagets/ACE_GamepadPhysicalButton.layout.meta b/addons/core/UI/layouts/Gagets/ACE_GamepadPhysicalButton.layout.meta new file mode 100644 index 00000000..e2f28847 --- /dev/null +++ b/addons/core/UI/layouts/Gagets/ACE_GamepadPhysicalButton.layout.meta @@ -0,0 +1,17 @@ +MetaFileClass { + Name "{7063BDFDF0B3998C}UI/layouts/Gagets/ACE_ControllerButton.layout" + Configurations { + LayoutResourceClass PC { + } + LayoutResourceClass XBOX_ONE : PC { + } + LayoutResourceClass XBOX_SERIES : PC { + } + LayoutResourceClass PS4 : PC { + } + LayoutResourceClass PS5 : PC { + } + LayoutResourceClass HEADLESS : PC { + } + } +} \ No newline at end of file diff --git a/addons/core/UI/layouts/Gagets/ACE_InspectGadgetMenu.layout b/addons/core/UI/layouts/Gagets/ACE_InspectGadgetMenu.layout new file mode 100644 index 00000000..1af99e87 --- /dev/null +++ b/addons/core/UI/layouts/Gagets/ACE_InspectGadgetMenu.layout @@ -0,0 +1,8 @@ +FrameWidgetClass { + Name "rootFrame" + "Ignore Cursor" 0 + components { + ACE_PhysicalButtonsUIComponent "{6563032AD753AEC4}" { + } + } +} \ No newline at end of file diff --git a/addons/core/UI/layouts/Gagets/ACE_InspectGadgetMenu.layout.meta b/addons/core/UI/layouts/Gagets/ACE_InspectGadgetMenu.layout.meta new file mode 100644 index 00000000..e680d92a --- /dev/null +++ b/addons/core/UI/layouts/Gagets/ACE_InspectGadgetMenu.layout.meta @@ -0,0 +1,17 @@ +MetaFileClass { + Name "{6661B07C2C6D0CC7}UI/layouts/Gagets/ACE_InspectGadgetMenu.layout" + Configurations { + LayoutResourceClass PC { + } + LayoutResourceClass XBOX_ONE : PC { + } + LayoutResourceClass XBOX_SERIES : PC { + } + LayoutResourceClass PS4 : PC { + } + LayoutResourceClass PS5 : PC { + } + LayoutResourceClass HEADLESS : PC { + } + } +} \ No newline at end of file diff --git a/addons/core/scripts/Game/ACE_Core/Components/ACE_PhysicalButtonsComponent.c b/addons/core/scripts/Game/ACE_Core/Components/ACE_PhysicalButtonsComponent.c new file mode 100644 index 00000000..5f50e61a --- /dev/null +++ b/addons/core/scripts/Game/ACE_Core/Components/ACE_PhysicalButtonsComponent.c @@ -0,0 +1,114 @@ +//------------------------------------------------------------------------------------------------ +class ACE_PhysicalButtonsComponentClass : ScriptComponentClass +{ +} + +//------------------------------------------------------------------------------------------------ +//! Component for registering physical buttons on an entity +class ACE_PhysicalButtonsComponent : ScriptComponent +{ + [Attribute()] + protected ref array m_aButtonConfigs; + + //------------------------------------------------------------------------------------------------ + override protected void OnPostInit(IEntity owner) + { + super.OnPostInit(owner); + SetEventMask(owner, EntityEvent.INIT); + } + + //------------------------------------------------------------------------------------------------ + override protected void EOnInit(IEntity owner) + { + super.EOnInit(owner); + + if (!GetGame().InPlayMode()) + return; + + foreach (ACE_PhysicalButtonConfig buttonConfig : m_aButtonConfigs) + { + buttonConfig.Init(owner); + } + } + + //------------------------------------------------------------------------------------------------ + void SetButtonState(string colliderName, bool state) + { + ACE_PhysicalButtonConfig config = GetButtonConfig(colliderName); + if (!config) + return; + + Animation anim = GetOwner().GetAnimation(); + if (!anim) + return; + + TNodeId boneID = anim.GetBoneIndex(config.m_sBoneName); + vector transform[4]; + Math3D.MatrixIdentity4(transform); + + if (state) + transform[3] = config.m_vPressedOffset; + + anim.SetBoneMatrix(GetOwner(), boneID, transform); + + if (!state) + PrintFormat("Button %1 clicked", colliderName, level: LogLevel.DEBUG); + + if (state) + AudioSystem.PlaySound(config.m_sPressedSoundPath); + else + AudioSystem.PlaySound(config.m_sReleasedSoundPath); + + if (!state && config.m_Command) + config.m_Command.Execute(); + } + + //------------------------------------------------------------------------------------------------ + ACE_PhysicalButtonConfig GetButtonConfig(string colliderName) + { + foreach (ACE_PhysicalButtonConfig buttonConfig : m_aButtonConfigs) + { + if (buttonConfig.m_sColliderName == colliderName) + return buttonConfig; + } + + return null; + } + + //------------------------------------------------------------------------------------------------ + array GetAllButtonConfigs() + { + return m_aButtonConfigs; + } +} + +//------------------------------------------------------------------------------------------------ +//! Config for a physical button +[BaseContainerProps(), BaseContainerCustomTitleField("m_sColliderName")] +class ACE_PhysicalButtonConfig +{ + [Attribute(desc: "Name of the collider of the button")] + string m_sColliderName; + + [Attribute(desc: "Name of the button's bone")] + string m_sBoneName; + + [Attribute(defvalue: "0 -0.001 0", desc: "Displacement vector for when the button is pressed button")] + vector m_vPressedOffset; + + [Attribute(defvalue: "{9DDDC275FFCC545F}Sounds/Props/Military/Radios/Samples/Props_Radios_Button_Press_1.wav", desc: "Sound for when the button is pressed", uiwidget: UIWidgets.ResourceNamePicker, params: "wav")] + ResourceName m_sPressedSoundPath; + + [Attribute(defvalue: "{A8CB8AE57458C226}Sounds/Props/Military/Radios/Samples/Props_Radios_Button_Release_1.wav", desc: "Sound for when the button is released", uiwidget: UIWidgets.ResourceNamePicker, params: "wav")] + ResourceName m_sReleasedSoundPath; + + [Attribute(desc: "Command that the button executes")] + ref ACE_IGadgetCommand m_Command; + + //------------------------------------------------------------------------------------------------ + void Init(IEntity gadget) + { + if (m_Command) + m_Command.Init(gadget); + } +} diff --git a/addons/core/scripts/Game/ACE_Core/Components/Gadgets/Commands/ACE_IGadgetCommand.c b/addons/core/scripts/Game/ACE_Core/Components/Gadgets/Commands/ACE_IGadgetCommand.c new file mode 100644 index 00000000..8339d942 --- /dev/null +++ b/addons/core/scripts/Game/ACE_Core/Components/Gadgets/Commands/ACE_IGadgetCommand.c @@ -0,0 +1,14 @@ +//------------------------------------------------------------------------------------------------ +class ACE_IGadgetCommand : ScriptAndConfig +{ + protected IEntity m_Gadget; + + //------------------------------------------------------------------------------------------------ + void Init(IEntity gadget) + { + m_Gadget = gadget; + } + + //------------------------------------------------------------------------------------------------ + void Execute(); +} \ No newline at end of file diff --git a/addons/core/scripts/Game/ACE_Core/UI/Components/ACE_GamepadPhysicalButtonUIComponent.c b/addons/core/scripts/Game/ACE_Core/UI/Components/ACE_GamepadPhysicalButtonUIComponent.c new file mode 100644 index 00000000..8b854738 --- /dev/null +++ b/addons/core/scripts/Game/ACE_Core/UI/Components/ACE_GamepadPhysicalButtonUIComponent.c @@ -0,0 +1,45 @@ +//------------------------------------------------------------------------------------------------ +class ACE_GamepadPhysicalButtonUIComponent : ScriptedWidgetComponent +{ + protected Widget m_wWidget; + protected ACE_PhysicalButtonsComponent m_PhysicalButtonsComponent; + protected ACE_PhysicalButtonConfig m_ButtonConfig; + + protected static const int BUTTON_RELEASE_DELAY_MS = 250; + + //------------------------------------------------------------------------------------------------ + override void HandlerAttached(Widget w) + { + m_wWidget = w; + } + + //------------------------------------------------------------------------------------------------ + void SetPhysicalButton(ACE_PhysicalButtonsComponent component, ACE_PhysicalButtonConfig buttonConfig) + { + m_PhysicalButtonsComponent = component; + m_ButtonConfig = buttonConfig; + m_wWidget.SetName(buttonConfig.m_sColliderName); + } + + //------------------------------------------------------------------------------------------------ + override bool OnClick(Widget w, int x, int y, int button) + { + m_PhysicalButtonsComponent.SetButtonState(m_ButtonConfig.m_sColliderName, true); + GetGame().GetCallqueue().CallLater(m_PhysicalButtonsComponent.SetButtonState, BUTTON_RELEASE_DELAY_MS, false, m_ButtonConfig.m_sColliderName, false); + return true; + } + + //------------------------------------------------------------------------------------------------ + override bool OnFocus(Widget w, int x, int y) + { + m_wWidget.SetOpacity(1); + return false; + } + + //------------------------------------------------------------------------------------------------ + override bool OnFocusLost(Widget w, int x, int y) + { + m_wWidget.SetOpacity(0); + return false; + } +} diff --git a/addons/core/scripts/Game/ACE_Core/UI/Components/ACE_PhysicalButtonsUIComponent.c b/addons/core/scripts/Game/ACE_Core/UI/Components/ACE_PhysicalButtonsUIComponent.c new file mode 100644 index 00000000..a9f3068f --- /dev/null +++ b/addons/core/scripts/Game/ACE_Core/UI/Components/ACE_PhysicalButtonsUIComponent.c @@ -0,0 +1,118 @@ +//------------------------------------------------------------------------------------------------ +//! Widget component for forwarding left mouse button clicks to physical buttons +class ACE_PhysicalButtonsUIComponent : ScriptedWidgetComponent +{ + [Attribute("1", desc: "Length of tracer for detecting physical buttons [m]")] + protected float m_fTracerLength; + + [Attribute(defvalue: "{7063BDFDF0B3998C}UI/layouts/Gagets/ACE_GamepadPhysicalButton.layout", uiwidget: UIWidgets.ResourceNamePicker, params: "layout")] + protected ResourceName m_sGamepadButtonLayout; + + protected WorkspaceWidget m_wWorkspace; + protected Widget m_wWidget; + protected ACE_PhysicalButtonsComponent m_PhysicalButtonsComponent; + protected string m_sActiveButtonColliderName; + + //------------------------------------------------------------------------------------------------ + override void HandlerAttached(Widget w) + { + m_wWidget = w; + m_wWorkspace = GetGame().GetWorkspace(); + } + + //------------------------------------------------------------------------------------------------ + void SetPhysicalButtonsComponent(notnull ACE_PhysicalButtonsComponent component) + { + m_PhysicalButtonsComponent = component; + + IEntity owner = m_PhysicalButtonsComponent.GetOwner(); + if (!owner) + return; + + Animation ownerAnim = owner.GetAnimation(); + if (!ownerAnim) + return; + + Widget button; + array configs = m_PhysicalButtonsComponent.GetAllButtonConfigs(); + int numButtons = configs.Count(); + vector cursorPos; + + foreach (int i, ACE_PhysicalButtonConfig config : m_PhysicalButtonsComponent.GetAllButtonConfigs()) + { + // Project position of button bone on screen + vector modelTransform[4]; + ownerAnim.GetBoneMatrix(ownerAnim.GetBoneIndex(config.m_sBoneName), modelTransform); + vector worldPos = owner.CoordToParent(modelTransform[3]); + vector screenPos = m_wWorkspace.ProjWorldToScreen(worldPos, owner.GetWorld()); + cursorPos += m_wWorkspace.DPIScale(1) * screenPos / numButtons; + + // Create helper button widgets on console + #ifdef PLATFORM_CONSOLE + button = m_wWorkspace.CreateWidgets(m_sGamepadButtonLayout, m_wWidget); + FrameSlot.SetPos(button, screenPos[0], screenPos[1]); + + string nextButtonName = configs[(numButtons + i + 1) % numButtons].m_sColliderName; + button.SetNavigation(WidgetNavigationDirection.UP, WidgetNavigationRuleType.EXPLICIT, nextButtonName); + button.SetNavigation(WidgetNavigationDirection.RIGHT, WidgetNavigationRuleType.EXPLICIT, nextButtonName); + string prevButtonName = configs[(numButtons + i - 1) % numButtons].m_sColliderName; + button.SetNavigation(WidgetNavigationDirection.DOWN, WidgetNavigationRuleType.EXPLICIT, prevButtonName); + button.SetNavigation(WidgetNavigationDirection.LEFT, WidgetNavigationRuleType.EXPLICIT, prevButtonName); + + ACE_GamepadPhysicalButtonUIComponent handler = ACE_GamepadPhysicalButtonUIComponent.Cast(button.FindHandler(ACE_GamepadPhysicalButtonUIComponent)); + if (handler) + handler.SetPhysicalButton(m_PhysicalButtonsComponent, config); + #endif + } + + #ifdef PLATFORM_CONSOLE + if (button) + m_wWorkspace.SetFocusedWidget(button); + #endif + + // Move cursor to centroid of all buttons + if (cursorPos) + GetGame().GetInputManager().SetCursorPosition(cursorPos[0], cursorPos[1]); + } + + //------------------------------------------------------------------------------------------------ + override bool OnMouseButtonDown(Widget w, int x, int y, int button) + { + if (button != SCR_EMouseButtons.LEFT || !m_PhysicalButtonsComponent || !m_sActiveButtonColliderName.IsEmpty()) + return false; + + m_sActiveButtonColliderName = TraceButtonCollider(x, y); + if (m_sActiveButtonColliderName.IsEmpty()) + return false; + + m_PhysicalButtonsComponent.SetButtonState(m_sActiveButtonColliderName, 1); + return true; + } + + //------------------------------------------------------------------------------------------------ + override bool OnMouseButtonUp(Widget w, int x, int y, int button) + { + if (button != SCR_EMouseButtons.LEFT || !m_PhysicalButtonsComponent || m_sActiveButtonColliderName.IsEmpty()) + return false; + + m_PhysicalButtonsComponent.SetButtonState(m_sActiveButtonColliderName, 0); + m_sActiveButtonColliderName = ""; + return true; + } + + //------------------------------------------------------------------------------------------------ + protected string TraceButtonCollider(int x, int y) + { + vector cameraDir; + vector cameraPos = m_wWorkspace.ProjScreenToWorldNative(x, y, cameraDir, GetGame().GetWorld()); + + TraceParam params = new TraceParam(); + params.Include = m_PhysicalButtonsComponent.GetOwner(); + params.Flags = TraceFlags.ENTS; + params.TargetLayers = EPhysicsLayerDefs.Interaction; + params.Start = cameraPos; + params.End = cameraPos + m_fTracerLength * cameraDir; + GetGame().GetWorld().TraceMove(params, null); + return params.ColliderName; + } +} diff --git a/addons/core/scripts/Game/ACE_Core/UI/Menu/ACE_InspectGadgetMenu.c b/addons/core/scripts/Game/ACE_Core/UI/Menu/ACE_InspectGadgetMenu.c new file mode 100644 index 00000000..dd3f754e --- /dev/null +++ b/addons/core/scripts/Game/ACE_Core/UI/Menu/ACE_InspectGadgetMenu.c @@ -0,0 +1,25 @@ +//------------------------------------------------------------------------------------------------ +class ACE_InspectGadgetMenu : ChimeraMenuBase +{ + //------------------------------------------------------------------------------------------------ + override void OnMenuOpen() + { + super.OnMenuOpen(); + GetGame().GetInputManager().AddActionListener("MenuOpen", EActionTrigger.DOWN, Close); + GetGame().GetInputManager().AddActionListener("MenuBack", EActionTrigger.DOWN, Close); + } + + //------------------------------------------------------------------------------------------------ + override void OnMenuClose() + { + super.OnMenuClose(); + GetGame().GetInputManager().RemoveActionListener("MenuOpen", EActionTrigger.DOWN, Close); + GetGame().GetInputManager().RemoveActionListener("MenuBack", EActionTrigger.DOWN, Close); + } + + //------------------------------------------------------------------------------------------------ + ACE_PhysicalButtonsUIComponent GetPhysicalButtonsUIComponent() + { + return ACE_PhysicalButtonsUIComponent.Cast(GetRootWidget().FindHandler(ACE_PhysicalButtonsUIComponent)); + } +} diff --git a/addons/core/scripts/Game/ACE_Core/UI/Menu/ChimeraMenuPreset.c b/addons/core/scripts/Game/ACE_Core/UI/Menu/ChimeraMenuPreset.c new file mode 100644 index 00000000..4fc64993 --- /dev/null +++ b/addons/core/scripts/Game/ACE_Core/UI/Menu/ChimeraMenuPreset.c @@ -0,0 +1,5 @@ +//------------------------------------------------------------------------------------------------ +modded enum ChimeraMenuPreset : ScriptMenuPresetEnum +{ + ACE_InspectGadgetMenu +}