Dynamically populate the ingame menu.

This commit is contained in:
Paul Chote
2019-04-14 21:03:13 +00:00
committed by reaperrr
parent 1cfb110d03
commit 877215c86a
4 changed files with 221 additions and 245 deletions

View File

@@ -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<ButtonWidget> buttons = new List<ButtonWidget>();
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<string, MiniYaml> logicArgs)
{
var leaving = false;
this.modData = modData;
this.world = world;
this.worldRenderer = worldRenderer;
this.onExit = onExit;
var buttonHandlers = new Dictionary<string, Action>
{
{ "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<MenuPaletteEffect>();
mpe = world.WorldActor.TraitOrDefault<MenuPaletteEffect>();
if (mpe != null)
mpe.Fade(mpe.Info.MenuEffect);
menu.Get<LabelWidget>("VERSION_LABEL").Text = modData.Manifest.Metadata.Version;
var hideMenu = false;
menu.Get("MENU_BUTTONS").IsVisible = () => !hideMenu;
buttonContainer = menu.Get("MENU_BUTTONS");
buttonTemplate = buttonContainer.Get<ButtonWidget>("BUTTON_TEMPLATE");
buttonContainer.RemoveChild(buttonTemplate);
buttonContainer.IsVisible = () => !hideMenu;
MiniYaml buttonStrideNode;
if (logicArgs.TryGetValue("ButtonStride", out buttonStrideNode))
buttonStride = FieldLoader.GetValue<int2>("ButtonStride", buttonStrideNode.Value);
var scriptContext = world.WorldActor.TraitOrDefault<LuaScript>();
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<string[]>("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<MissionObjectivesInfo>();
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<IObjectivesPanel>().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<MissionObjectivesInfo>();
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<IObjectivesPanel>().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<ButtonWidget>("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<ButtonWidget>("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<ButtonWidget>("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<ButtonWidget>("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<EditorActorLayer>();
@@ -182,49 +306,23 @@ namespace OpenRA.Mods.Common.Widgets.Logic
{ "actorDefinitions", editorActorLayer.Save() }
});
};
}
var musicButton = menu.Get<ButtonWidget>("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<ButtonWidget>("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<ButtonWidget>("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;
}
}
}
}

View File

@@ -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

View File

@@ -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

View File

@@ -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