diff --git a/OpenRA.Mods.Common/Widgets/Logic/Ingame/IngameMenuLogic.cs b/OpenRA.Mods.Common/Widgets/Logic/Ingame/IngameMenuLogic.cs index 67fcf0b41e..b2c48d50e1 100644 --- a/OpenRA.Mods.Common/Widgets/Logic/Ingame/IngameMenuLogic.cs +++ b/OpenRA.Mods.Common/Widgets/Logic/Ingame/IngameMenuLogic.cs @@ -10,6 +10,7 @@ #endregion using System; +using System.Collections.Generic; using System.Linq; using OpenRA.Graphics; using OpenRA.Mods.Common.Scripting; @@ -21,79 +22,169 @@ namespace OpenRA.Mods.Common.Widgets.Logic public class IngameMenuLogic : ChromeLogic { readonly Widget menu; + readonly Widget buttonContainer; + readonly ButtonWidget buttonTemplate; + readonly int2 buttonStride; + readonly List buttons = new List(); + + readonly ModData modData; + readonly Action onExit; + readonly World world; + readonly WorldRenderer worldRenderer; + readonly MenuPaletteEffect mpe; + bool hasError; + bool leaving; + bool hideMenu; [ObjectCreator.UseCtor] - public IngameMenuLogic(Widget widget, ModData modData, World world, Action onExit, WorldRenderer worldRenderer, IngameInfoPanel activePanel) + public IngameMenuLogic(Widget widget, ModData modData, World world, Action onExit, WorldRenderer worldRenderer, + IngameInfoPanel activePanel, Dictionary logicArgs) { - var leaving = false; + this.modData = modData; + this.world = world; + this.worldRenderer = worldRenderer; + this.onExit = onExit; + + var buttonHandlers = new Dictionary + { + { "ABORT_MISSION", CreateAbortMissionButton }, + { "SURRENDER", CreateSurrenderButton }, + { "MUSIC", CreateMusicButton }, + { "SETTINGS", CreateSettingsButton }, + { "RESUME", CreateResumeButton }, + { "SAVE_MAP", CreateSaveMapButton }, + { "EXIT_EDITOR", CreateExitEditorButton } + }; + menu = widget.Get("INGAME_MENU"); - var mpe = world.WorldActor.TraitOrDefault(); + mpe = world.WorldActor.TraitOrDefault(); if (mpe != null) mpe.Fade(mpe.Info.MenuEffect); menu.Get("VERSION_LABEL").Text = modData.Manifest.Metadata.Version; - var hideMenu = false; - menu.Get("MENU_BUTTONS").IsVisible = () => !hideMenu; + buttonContainer = menu.Get("MENU_BUTTONS"); + buttonTemplate = buttonContainer.Get("BUTTON_TEMPLATE"); + buttonContainer.RemoveChild(buttonTemplate); + buttonContainer.IsVisible = () => !hideMenu; + + MiniYaml buttonStrideNode; + if (logicArgs.TryGetValue("ButtonStride", out buttonStrideNode)) + buttonStride = FieldLoader.GetValue("ButtonStride", buttonStrideNode.Value); var scriptContext = world.WorldActor.TraitOrDefault(); - var hasError = scriptContext != null && scriptContext.FatalErrorOccurred; + hasError = scriptContext != null && scriptContext.FatalErrorOccurred; - // TODO: Create a mechanism to do things like this cleaner. Also needed for scripted missions - Action onQuit = () => + MiniYaml buttonsNode; + if (logicArgs.TryGetValue("Buttons", out buttonsNode)) { - if (world.Type == WorldType.Regular) + Action createHandler; + var buttonIds = FieldLoader.GetValue("Buttons", buttonsNode.Value); + foreach (var button in buttonIds) + if (buttonHandlers.TryGetValue(button, out createHandler)) + createHandler(); + } + + // Recenter the button container + if (buttons.Count > 0) + { + var expand = (buttons.Count - 1) * buttonStride; + buttonContainer.Bounds.X -= expand.X / 2; + buttonContainer.Bounds.Y -= expand.Y / 2; + buttonContainer.Bounds.Width += expand.X; + buttonContainer.Bounds.Height += expand.Y; + } + + var panelRoot = widget.GetOrNull("PANEL_ROOT"); + if (panelRoot != null && world.Type != WorldType.Editor) + { + var gameInfoPanel = Game.LoadWidget(world, "GAME_INFO_PANEL", panelRoot, new WidgetArgs() { - var moi = world.Map.Rules.Actors["player"].TraitInfoOrDefault(); - if (moi != null) - { - var faction = world.LocalPlayer == null ? null : world.LocalPlayer.Faction.InternalName; - Game.Sound.PlayNotification(world.Map.Rules, null, "Speech", moi.LeaveNotification, faction); - } - } + { "activePanel", activePanel } + }); - leaving = true; + gameInfoPanel.IsVisible = () => !hideMenu; + } + } - var iop = world.WorldActor.TraitsImplementing().FirstOrDefault(); - var exitDelay = iop != null ? iop.ExitDelay : 0; - if (mpe != null) + void OnQuit() + { + // TODO: Create a mechanism to do things like this cleaner. Also needed for scripted missions + if (world.Type == WorldType.Regular) + { + var moi = world.Map.Rules.Actors["player"].TraitInfoOrDefault(); + if (moi != null) { - Game.RunAfterDelay(exitDelay, () => - { - if (Game.IsCurrentWorld(world)) - mpe.Fade(MenuPaletteEffect.EffectType.Black); - }); - exitDelay += 40 * mpe.Info.FadeLength; + var faction = world.LocalPlayer == null ? null : world.LocalPlayer.Faction.InternalName; + Game.Sound.PlayNotification(world.Map.Rules, null, "Speech", moi.LeaveNotification, faction); } + } + leaving = true; + + var iop = world.WorldActor.TraitsImplementing().FirstOrDefault(); + var exitDelay = iop != null ? iop.ExitDelay : 0; + if (mpe != null) + { Game.RunAfterDelay(exitDelay, () => { - if (!Game.IsCurrentWorld(world)) - return; - - Game.Disconnect(); - Ui.ResetAll(); - Game.LoadShellMap(); + if (Game.IsCurrentWorld(world)) + mpe.Fade(MenuPaletteEffect.EffectType.Black); }); - }; + exitDelay += 40 * mpe.Info.FadeLength; + } - Action closeMenu = () => + Game.RunAfterDelay(exitDelay, () => { - Ui.CloseWindow(); - if (mpe != null) - mpe.Fade(MenuPaletteEffect.EffectType.None); - onExit(); - }; + if (!Game.IsCurrentWorld(world)) + return; - Action showMenu = () => hideMenu = false; + Game.Disconnect(); + Ui.ResetAll(); + Game.LoadShellMap(); + }); + } - var abortMissionButton = menu.Get("ABORT_MISSION"); - abortMissionButton.IsVisible = () => world.Type == WorldType.Regular; - abortMissionButton.IsDisabled = () => leaving; - if (world.IsGameOver) - abortMissionButton.GetText = () => "Leave"; + void ShowMenu() + { + hideMenu = false; + } - abortMissionButton.OnClick = () => + void CloseMenu() + { + Ui.CloseWindow(); + if (mpe != null) + mpe.Fade(MenuPaletteEffect.EffectType.None); + onExit(); + } + + ButtonWidget AddButton(string id, string text) + { + var button = buttonTemplate.Clone() as ButtonWidget; + var lastButton = buttons.LastOrDefault(); + if (lastButton != null) + { + button.Bounds.X = lastButton.Bounds.X + buttonStride.X; + button.Bounds.Y = lastButton.Bounds.Y + buttonStride.Y; + } + + button.Id = id; + button.IsDisabled = () => leaving; + button.GetText = () => text; + buttonContainer.AddChild(button); + buttons.Add(button); + + return button; + } + + void CreateAbortMissionButton() + { + if (world.Type != WorldType.Regular) + return; + + var button = AddButton("ABORT_MISSION", world.IsGameOver ? "Leave" : "Abort Mission"); + + button.OnClick = () => { hideMenu = true; @@ -122,54 +213,87 @@ namespace OpenRA.Mods.Common.Widgets.Logic ConfirmationDialogs.ButtonPrompt( title: "Leave Mission", text: "Leave this game and return to the menu?", - onConfirm: onQuit, - onCancel: showMenu, + onConfirm: OnQuit, + onCancel: ShowMenu, confirmText: "Leave", cancelText: "Stay", otherText: "Restart", onOther: restartAction); } else - onQuit(); + OnQuit(); }; + } - var exitEditorButton = menu.Get("EXIT_EDITOR"); - exitEditorButton.IsVisible = () => world.Type == WorldType.Editor; - exitEditorButton.OnClick = () => - { - hideMenu = true; - ConfirmationDialogs.ButtonPrompt( - title: "Exit Map Editor", - text: "Exit and lose all unsaved changes?", - onConfirm: onQuit, - onCancel: showMenu); - }; + void CreateSurrenderButton() + { + if (world.Type != WorldType.Regular || world.Map.Visibility.HasFlag(MapVisibility.MissionSelector) || world.LocalPlayer == null) + return; Action onSurrender = () => { world.IssueOrder(new Order("Surrender", world.LocalPlayer.PlayerActor, false)); - closeMenu(); + CloseMenu(); }; - var surrenderButton = menu.Get("SURRENDER"); - surrenderButton.IsVisible = () => world.Type == WorldType.Regular; - surrenderButton.IsDisabled = () => - world.LocalPlayer == null || world.LocalPlayer.WinState != WinState.Undefined || - world.Map.Visibility.HasFlag(MapVisibility.MissionSelector) || hasError; - surrenderButton.OnClick = () => + + var button = AddButton("SURRENDER", "Surrender"); + button.IsDisabled = () => world.LocalPlayer.WinState != WinState.Undefined || hasError || leaving; + button.OnClick = () => { hideMenu = true; ConfirmationDialogs.ButtonPrompt( title: "Surrender", text: "Are you sure you want to surrender?", onConfirm: onSurrender, - onCancel: showMenu, + onCancel: ShowMenu, confirmText: "Surrender", cancelText: "Stay"); }; + } - var saveMapButton = menu.Get("SAVE_MAP"); - saveMapButton.IsVisible = () => world.Type == WorldType.Editor; - saveMapButton.OnClick = () => + void CreateMusicButton() + { + var button = AddButton("MUSIC", "Music"); + button.OnClick = () => + { + hideMenu = true; + Ui.OpenWindow("MUSIC_PANEL", new WidgetArgs() + { + { "onExit", () => hideMenu = false }, + { "world", world } + }); + }; + } + + void CreateSettingsButton() + { + var button = AddButton("SETTINGS", "Settings"); + button.OnClick = () => + { + hideMenu = true; + Ui.OpenWindow("SETTINGS_PANEL", new WidgetArgs() + { + { "world", world }, + { "worldRenderer", worldRenderer }, + { "onExit", () => hideMenu = false }, + }); + }; + } + + void CreateResumeButton() + { + var button = AddButton("RESUME", world.IsGameOver ? "Return to map" : "Resume"); + button.Key = modData.Hotkeys["escape"]; + button.OnClick = CloseMenu; + } + + void CreateSaveMapButton() + { + if (world.Type != WorldType.Editor) + return; + + var button = AddButton("SAVE_MAP", "Save Map"); + button.OnClick = () => { hideMenu = true; var editorActorLayer = world.WorldActor.Trait(); @@ -182,49 +306,23 @@ namespace OpenRA.Mods.Common.Widgets.Logic { "actorDefinitions", editorActorLayer.Save() } }); }; + } - var musicButton = menu.Get("MUSIC"); - musicButton.IsDisabled = () => leaving; - musicButton.OnClick = () => + void CreateExitEditorButton() + { + if (world.Type != WorldType.Editor) + return; + + var button = AddButton("EXIT_EDITOR", "Exit Map Editor"); + button.OnClick = () => { hideMenu = true; - Ui.OpenWindow("MUSIC_PANEL", new WidgetArgs() - { - { "onExit", () => hideMenu = false }, - { "world", world } - }); + ConfirmationDialogs.ButtonPrompt( + title: "Exit Map Editor", + text: "Exit and lose all unsaved changes?", + onConfirm: OnQuit, + onCancel: ShowMenu); }; - - var settingsButton = widget.Get("SETTINGS"); - settingsButton.IsDisabled = () => leaving; - settingsButton.OnClick = () => - { - hideMenu = true; - Ui.OpenWindow("SETTINGS_PANEL", new WidgetArgs() - { - { "world", world }, - { "worldRenderer", worldRenderer }, - { "onExit", () => hideMenu = false }, - }); - }; - - var resumeButton = menu.Get("RESUME"); - resumeButton.IsDisabled = () => leaving; - if (world.IsGameOver) - resumeButton.GetText = () => "Return to map"; - - resumeButton.OnClick = closeMenu; - - var panelRoot = widget.GetOrNull("PANEL_ROOT"); - if (panelRoot != null && world.Type != WorldType.Editor) - { - var gameInfoPanel = Game.LoadWidget(world, "GAME_INFO_PANEL", panelRoot, new WidgetArgs() - { - { "activePanel", activePanel } - }); - - gameInfoPanel.IsVisible = () => !hideMenu; - } } } } diff --git a/mods/cnc/chrome/ingame-menu.yaml b/mods/cnc/chrome/ingame-menu.yaml index 2629168368..c348620c31 100644 --- a/mods/cnc/chrome/ingame-menu.yaml +++ b/mods/cnc/chrome/ingame-menu.yaml @@ -2,6 +2,8 @@ Container@INGAME_MENU: Width: WINDOW_RIGHT Height: WINDOW_BOTTOM Logic: IngameMenuLogic + Buttons: EXIT_EDITOR, SAVE_MAP, ABORT_MISSION, SURRENDER, LOAD_GAME, SAVE_GAME, MUSIC, SETTINGS, RESUME + ButtonStride: 130, 0 Children: Image@EVA: X: WINDOW_RIGHT - 128 - 43 @@ -24,49 +26,9 @@ Container@INGAME_MENU: Container@MENU_BUTTONS: X: (WINDOW_RIGHT - WIDTH) / 2 Y: WINDOW_BOTTOM - 33 - HEIGHT - 10 - Width: 740 + Width: 120 Height: 35 Children: - Button@ABORT_MISSION: - X: 0 - Y: 0 - Width: 140 + Button@BUTTON_TEMPLATE: + Width: 120 Height: 35 - Text: Abort Mission - Button@EXIT_EDITOR: - X: 0 - Y: 0 - Width: 140 - Height: 35 - Text: Exit Map Editor - Button@SURRENDER: - X: 150 - Y: 0 - Width: 140 - Height: 35 - Text: Surrender - Button@SAVE_MAP: - X: 150 - Y: 0 - Width: 140 - Height: 35 - Text: Save Map - Button@MUSIC: - X: 300 - Y: 0 - Width: 140 - Height: 35 - Text: Music - Button@SETTINGS: - X: 450 - Y: 0 - Width: 140 - Height: 35 - Text: Settings - Button@RESUME: - Key: escape - X: 600 - Y: 0 - Width: 140 - Height: 35 - Text: Resume diff --git a/mods/common/chrome/ingame-menu.yaml b/mods/common/chrome/ingame-menu.yaml index 153dac33e3..432cf3b0f6 100644 --- a/mods/common/chrome/ingame-menu.yaml +++ b/mods/common/chrome/ingame-menu.yaml @@ -2,6 +2,8 @@ Container@INGAME_MENU: Width: WINDOW_RIGHT Height: WINDOW_BOTTOM Logic: IngameMenuLogic + Buttons: RESUME, LOAD_GAME, SAVE_GAME, SETTINGS, MUSIC, SURRENDER, ABORT_MISSION, SAVE_MAP, EXIT_EDITOR + ButtonStride: 0, 40 Children: Background@BORDER: X: 0 - 15 @@ -27,7 +29,7 @@ Container@INGAME_MENU: X: 13 + (WINDOW_RIGHT - 522) / 4 - WIDTH / 2 Y: (WINDOW_BOTTOM - HEIGHT) / 2 Width: 200 - Height: 295 + Height: 120 Children: Label@LABEL_TITLE: X: (PARENT_RIGHT - WIDTH) / 2 @@ -37,53 +39,9 @@ Container@INGAME_MENU: Text: Options Align: Center Font: Bold - Button@RESUME: + Button@BUTTON_TEMPLATE: X: (PARENT_RIGHT - WIDTH) / 2 Y: 60 Width: 140 Height: 30 - Text: Resume - Font: Bold - Key: escape - Button@SETTINGS: - X: (PARENT_RIGHT - WIDTH) / 2 - Y: 100 - Width: 140 - Height: 30 - Text: Settings - Font: Bold - Button@MUSIC: - X: (PARENT_RIGHT - WIDTH) / 2 - Y: 140 - Width: 140 - Height: 30 - Text: Music - Font: Bold - Button@SURRENDER: - X: (PARENT_RIGHT - WIDTH) / 2 - Y: 180 - Width: 140 - Height: 30 - Text: Surrender - Font: Bold - Button@SAVE_MAP: - X: (PARENT_RIGHT - WIDTH) / 2 - Y: 180 - Width: 140 - Height: 30 - Text: Save Map - Font: Bold - Button@ABORT_MISSION: - X: (PARENT_RIGHT - WIDTH) / 2 - Y: 220 - Width: 140 - Height: 30 - Text: Abort Mission - Font: Bold - Button@EXIT_EDITOR: - X: (PARENT_RIGHT - WIDTH) / 2 - Y: 220 - Width: 140 - Height: 30 - Text: Exit Map Editor Font: Bold diff --git a/mods/d2k/chrome/ingame-menu.yaml b/mods/d2k/chrome/ingame-menu.yaml index 46b1961053..1720dc426f 100644 --- a/mods/d2k/chrome/ingame-menu.yaml +++ b/mods/d2k/chrome/ingame-menu.yaml @@ -2,6 +2,8 @@ Container@INGAME_MENU: Width: WINDOW_RIGHT Height: WINDOW_BOTTOM Logic: IngameMenuLogic + Buttons: RESUME, LOAD_GAME, SAVE_GAME, SETTINGS, MUSIC, SURRENDER, ABORT_MISSION, SAVE_MAP, EXIT_EDITOR + ButtonStride: 0, 40 Children: Label@VERSION_LABEL: X: WINDOW_RIGHT - 10 @@ -14,7 +16,7 @@ Container@INGAME_MENU: X: 13 + (WINDOW_RIGHT - 522) / 4 - WIDTH / 2 Y: (WINDOW_BOTTOM - HEIGHT) / 2 Width: 200 - Height: 295 + Height: 120 Children: Label@LABEL_TITLE: X: (PARENT_RIGHT - WIDTH) / 2 @@ -24,53 +26,9 @@ Container@INGAME_MENU: Text: Options Align: Center Font: Bold - Button@RESUME: + Button@BUTTON_TEMPLATE: X: (PARENT_RIGHT - WIDTH) / 2 Y: 60 Width: 140 Height: 30 - Text: Resume - Font: Bold - Key: escape - Button@SETTINGS: - X: (PARENT_RIGHT - WIDTH) / 2 - Y: 100 - Width: 140 - Height: 30 - Text: Settings - Font: Bold - Button@MUSIC: - X: (PARENT_RIGHT - WIDTH) / 2 - Y: 140 - Width: 140 - Height: 30 - Text: Music - Font: Bold - Button@SURRENDER: - X: (PARENT_RIGHT - WIDTH) / 2 - Y: 180 - Width: 140 - Height: 30 - Text: Surrender - Font: Bold - Button@SAVE_MAP: - X: (PARENT_RIGHT - WIDTH) / 2 - Y: 180 - Width: 140 - Height: 30 - Text: Save Map - Font: Bold - Button@ABORT_MISSION: - X: (PARENT_RIGHT - WIDTH) / 2 - Y: 220 - Width: 140 - Height: 30 - Text: Abort Mission - Font: Bold - Button@EXIT_EDITOR: - X: (PARENT_RIGHT - WIDTH) / 2 - Y: 220 - Width: 140 - Height: 30 - Text: Exit Map Editor Font: Bold