diff --git a/OpenRA.Mods.Common/Traits/World/EditorActionManager.cs b/OpenRA.Mods.Common/Traits/World/EditorActionManager.cs index d14455f50c..5f025126aa 100644 --- a/OpenRA.Mods.Common/Traits/World/EditorActionManager.cs +++ b/OpenRA.Mods.Common/Traits/World/EditorActionManager.cs @@ -31,10 +31,12 @@ namespace OpenRA.Mods.Common.Traits int nextId; public bool Modified; + public bool SaveFailed; public void WorldLoaded(World w, WorldRenderer wr) { Add(new OpenMapAction()); + Modified = false; } public void Add(IEditorAction editorAction) diff --git a/OpenRA.Mods.Common/Widgets/Logic/Editor/NewMapLogic.cs b/OpenRA.Mods.Common/Widgets/Logic/Editor/NewMapLogic.cs index 35a6a661ae..656d58286f 100644 --- a/OpenRA.Mods.Common/Widgets/Logic/Editor/NewMapLogic.cs +++ b/OpenRA.Mods.Common/Widgets/Logic/Editor/NewMapLogic.cs @@ -81,6 +81,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic { "onSave", afterSave }, { "onExit", () => { Ui.CloseWindow(); onExit(); } }, { "map", map }, + { "world", world }, { "playerDefinitions", map.PlayerDefinitions }, { "actorDefinitions", map.ActorDefinitions } }); diff --git a/OpenRA.Mods.Common/Widgets/Logic/Editor/SaveMapLogic.cs b/OpenRA.Mods.Common/Widgets/Logic/Editor/SaveMapLogic.cs index 611b9c21fb..e86ba32d3c 100644 --- a/OpenRA.Mods.Common/Widgets/Logic/Editor/SaveMapLogic.cs +++ b/OpenRA.Mods.Common/Widgets/Logic/Editor/SaveMapLogic.cs @@ -14,6 +14,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; using OpenRA.FileSystem; +using OpenRA.Mods.Common.Traits; using OpenRA.Widgets; namespace OpenRA.Mods.Common.Widgets.Logic @@ -49,7 +50,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic const string SaveMapFailedPrompt = "dialog-save-map-failed.prompt"; [TranslationReference] - const string SaveMapFailedAccept = "dialog-save-map-failed.confirm"; + const string SaveMapFailedConfirm = "dialog-save-map-failed.confirm"; [TranslationReference] const string Unpacked = "label-unpacked-map"; @@ -61,7 +62,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic const string OverwriteMapFailedPrompt = "dialog-overwrite-map-failed.prompt"; [TranslationReference] - const string SaveMapFailedConfirm = "dialog-overwrite-map-failed.confirm"; + const string OverwriteMapFailedConfirm = "dialog-overwrite-map-failed.confirm"; [TranslationReference] const string OverwriteMapOutsideEditTitle = "dialog-overwrite-map-outside-edit.title"; @@ -72,9 +73,12 @@ namespace OpenRA.Mods.Common.Widgets.Logic [TranslationReference] const string SaveMapMapOutsideConfirm = "dialog-overwrite-map-outside-edit.confirm"; + [TranslationReference] + const string SaveCurrentMap = "notification-save-current-map"; + [ObjectCreator.UseCtor] - public SaveMapLogic(Widget widget, ModData modData, Action onSave, Action onExit, - Map map, List playerDefinitions, List actorDefinitions) + public SaveMapLogic(Widget widget, ModData modData, Map map, Action onSave, Action onExit, + World world, List playerDefinitions, List actorDefinitions) { var title = widget.Get("TITLE"); title.Text = map.Title; @@ -202,7 +206,8 @@ namespace OpenRA.Mods.Common.Widgets.Logic if (playerDefinitions != null) map.PlayerDefinitions = playerDefinitions; - map.RequiresMod = modData.Manifest.Id; + Ui.CloseWindow(); + onExit(); try { @@ -215,22 +220,14 @@ namespace OpenRA.Mods.Common.Widgets.Logic package = new Folder(combinedPath); } - map.Save(package); - - Ui.CloseWindow(); - onSave(map.Uid); + SaveMapInner(map, package, world, modData); } catch (Exception e) { - Log.Write("debug", $"Failed to save map at {combinedPath}"); - Log.Write("debug", e); - - ConfirmationDialogs.ButtonPrompt(modData, - title: SaveMapFailedTitle, - text: SaveMapFailedPrompt, - onConfirm: () => { }, - confirmText: SaveMapFailedAccept); + SaveMapFailed(e, modData, world); } + + onSave(map.Uid); }; var save = widget.Get("SAVE_BUTTON"); @@ -239,41 +236,114 @@ namespace OpenRA.Mods.Common.Widgets.Logic save.OnClick = () => { var combinedPath = Platform.ResolvePath(Path.Combine(selectedDirectory.Folder.Name, filename.Text + fileTypes[fileType].Extension)); - - if (map.Package?.Name != combinedPath) - { - // When creating a new map or when file paths don't match - if (modData.MapCache.Any(m => m.Status == MapStatus.Available && m.Package?.Name == combinedPath)) - { - ConfirmationDialogs.ButtonPrompt(modData, - title: OverwriteMapFailedTitle, - text: OverwriteMapFailedPrompt, - confirmText: SaveMapFailedConfirm, - onConfirm: () => saveMap(combinedPath), - onCancel: () => { }); - - return; - } - } - else - { - // When file paths match - var recentUid = modData.MapCache.GetUpdatedMap(map.Uid); - if (recentUid != null && map.Uid != recentUid && modData.MapCache[recentUid].Status == MapStatus.Available) - { - ConfirmationDialogs.ButtonPrompt(modData, - title: OverwriteMapOutsideEditTitle, - text: OverwriteMapOutsideEditPrompt, - confirmText: SaveMapMapOutsideConfirm, - onConfirm: () => saveMap(combinedPath), - onCancel: () => { }); - - return; - } - } - - saveMap(combinedPath); + SaveMap(modData, world, map, combinedPath, saveMap); }; } + + public static void SaveMap(ModData modData, World world, Map map, string combinedPath, Action saveMap) + { + var actionManager = world.WorldActor.TraitOrDefault(); + + if (map.Package?.Name != combinedPath) + { + // When creating a new map or when file paths don't match + if (modData.MapCache.Any(m => m.Status == MapStatus.Available && m.Package?.Name == combinedPath)) + { + ConfirmationDialogs.ButtonPrompt(modData, + title: OverwriteMapFailedTitle, + text: OverwriteMapFailedPrompt, + confirmText: OverwriteMapFailedConfirm, + onConfirm: () => + { + saveMap(combinedPath); + if (actionManager != null) + actionManager.SaveFailed = false; + }, + onCancel: () => + { + if (actionManager != null) + actionManager.SaveFailed = false; + }); + + if (actionManager != null) + actionManager.SaveFailed = true; + + return; + } + } + else + { + // When file paths match + var recentUid = modData.MapCache.GetUpdatedMap(map.Uid); + if (recentUid != null && map.Uid != recentUid && modData.MapCache[recentUid].Status == MapStatus.Available) + { + ConfirmationDialogs.ButtonPrompt(modData, + title: OverwriteMapOutsideEditTitle, + text: OverwriteMapOutsideEditPrompt, + confirmText: SaveMapMapOutsideConfirm, + onConfirm: () => + { + saveMap(combinedPath); + if (actionManager != null) + actionManager.SaveFailed = false; + }, + onCancel: () => + { + if (actionManager != null) + actionManager.SaveFailed = false; + }); + + if (actionManager != null) + actionManager.SaveFailed = true; + + return; + } + } + + saveMap(combinedPath); + } + + public static void SaveMapInner(Map map, IReadWritePackage package, World world, ModData modData) + { + map.RequiresMod = modData.Manifest.Id; + + try + { + if (package == null) + throw new ArgumentNullException(nameof(package)); + + map.Save(package); + + var actionManager = world.WorldActor.TraitOrDefault(); + if (actionManager != null) + actionManager.Modified = false; + + TextNotificationsManager.AddTransientLine(modData.Translation.GetString(SaveCurrentMap), world.LocalPlayer); + } + catch (Exception e) + { + SaveMapFailed(e, modData, world); + } + } + + static void SaveMapFailed(Exception e, ModData modData, World world) + { + Log.Write("debug", $"Failed to save map."); + Log.Write("debug", e); + + var actionManager = world.WorldActor.TraitOrDefault(); + if (actionManager != null) + actionManager.SaveFailed = true; + + ConfirmationDialogs.ButtonPrompt(modData, + title: SaveMapFailedTitle, + text: SaveMapFailedPrompt, + onConfirm: () => + { + if (actionManager != null) + actionManager.SaveFailed = false; + }, + confirmText: SaveMapFailedConfirm); + } } } diff --git a/OpenRA.Mods.Common/Widgets/Logic/Ingame/Hotkeys/EditorQuickSaveHotkeyLogic.cs b/OpenRA.Mods.Common/Widgets/Logic/Ingame/Hotkeys/EditorQuickSaveHotkeyLogic.cs new file mode 100644 index 0000000000..599d06621e --- /dev/null +++ b/OpenRA.Mods.Common/Widgets/Logic/Ingame/Hotkeys/EditorQuickSaveHotkeyLogic.cs @@ -0,0 +1,62 @@ +#region Copyright & License Information +/* + * Copyright (c) The OpenRA Developers and Contributors + * This file is part of OpenRA, which is free software. It is made + * available to you 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. For more + * information, see COPYING. + */ +#endregion + +using System; +using System.Collections.Generic; +using OpenRA.FileSystem; +using OpenRA.Mods.Common.Lint; +using OpenRA.Mods.Common.Traits; +using OpenRA.Widgets; + +namespace OpenRA.Mods.Common.Widgets.Logic.Ingame +{ + [ChromeLogicArgsHotkeys("EditorQuickSaveKey")] + public class EditorQuickSaveHotkeyLogic : SingleHotkeyBaseLogic + { + readonly World world; + readonly ModData modData; + + [ObjectCreator.UseCtor] + public EditorQuickSaveHotkeyLogic(Widget widget, ModData modData, World world, Dictionary logicArgs) + : base(widget, modData, "QuickSaveKey", "GLOBAL_KEYHANDLER", logicArgs) + { + this.world = world; + this.modData = modData; + } + + protected override bool OnHotkeyActivated(KeyInput keyInput) + { + var actionManager = world.WorldActor.TraitOrDefault(); + if (actionManager != null && (!actionManager.Modified || actionManager.SaveFailed)) + return false; + + var map = world.Map; + Action saveMap = (string combinedPath) => + { + var editorActorLayer = world.WorldActor.Trait(); + + var actorDefinitions = editorActorLayer.Save(); + if (actorDefinitions != null) + map.ActorDefinitions = actorDefinitions; + + var playerDefinitions = editorActorLayer.Players.ToMiniYaml(); + if (playerDefinitions != null) + map.PlayerDefinitions = playerDefinitions; + + var package = (IReadWritePackage)map.Package; + SaveMapLogic.SaveMapInner(map, package, world, modData); + }; + + SaveMapLogic.SaveMap(modData, world, map, map.Package?.Name, saveMap); + return true; + } + } +} diff --git a/OpenRA.Mods.Common/Widgets/Logic/Ingame/IngameMenuLogic.cs b/OpenRA.Mods.Common/Widgets/Logic/Ingame/IngameMenuLogic.cs index b3b87fc866..19f8a84150 100644 --- a/OpenRA.Mods.Common/Widgets/Logic/Ingame/IngameMenuLogic.cs +++ b/OpenRA.Mods.Common/Widgets/Logic/Ingame/IngameMenuLogic.cs @@ -470,8 +470,9 @@ namespace OpenRA.Mods.Common.Widgets.Logic Ui.OpenWindow("SAVE_MAP_PANEL", new WidgetArgs() { { "onSave", (Action)(_ => { ShowMenu(); actionManager.Modified = false; }) }, - { "onExit", ShowMenu }, + { "onExit", CloseMenu }, { "map", world.Map }, + { "world", world }, { "playerDefinitions", playerDefinitions }, { "actorDefinitions", editorActorLayer.Save() } }); diff --git a/OpenRA.Mods.Common/Widgets/Logic/Ingame/LoadMapEditorLogic.cs b/OpenRA.Mods.Common/Widgets/Logic/Ingame/LoadMapEditorLogic.cs index 84a382a526..de26f77601 100644 --- a/OpenRA.Mods.Common/Widgets/Logic/Ingame/LoadMapEditorLogic.cs +++ b/OpenRA.Mods.Common/Widgets/Logic/Ingame/LoadMapEditorLogic.cs @@ -20,6 +20,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic { var editorRoot = widget.Get("WORLD_ROOT"); Game.LoadWidget(world, "EDITOR_WORLD_ROOT", editorRoot, new WidgetArgs()); + Game.LoadWidget(world, "TRANSIENTS_PANEL", editorRoot, new WidgetArgs()); } } } diff --git a/mods/cnc/chrome/editor.yaml b/mods/cnc/chrome/editor.yaml index fa0c120725..b4d81c91f0 100644 --- a/mods/cnc/chrome/editor.yaml +++ b/mods/cnc/chrome/editor.yaml @@ -197,13 +197,14 @@ Container@EDITOR_ROOT: Logic: LoadMapEditorLogic Children: LogicKeyListener@GLOBAL_KEYHANDLER: - Logic: MusicHotkeyLogic, ScreenshotHotkeyLogic, MuteHotkeyLogic + Logic: MusicHotkeyLogic, ScreenshotHotkeyLogic, MuteHotkeyLogic, EditorQuickSaveHotkeyLogic StopMusicKey: StopMusic PauseMusicKey: PauseMusic PrevMusicKey: PrevMusic NextMusicKey: NextMusic TakeScreenshotKey: TakeScreenshot MuteAudioKey: ToggleMute + QuickSaveKey: EditorQuickSave LogicKeyListener@WORLD_KEYHANDLER: Logic: ResetZoomHotkeyLogic ResetZoomKey: ResetZoom diff --git a/mods/common/chrome/editor.yaml b/mods/common/chrome/editor.yaml index e2d05ecb51..47ecca3aa3 100644 --- a/mods/common/chrome/editor.yaml +++ b/mods/common/chrome/editor.yaml @@ -188,13 +188,14 @@ Container@EDITOR_ROOT: Logic: LoadMapEditorLogic Children: LogicKeyListener@GLOBAL_KEYHANDLER: - Logic: MusicHotkeyLogic, ScreenshotHotkeyLogic, MuteHotkeyLogic + Logic: MusicHotkeyLogic, ScreenshotHotkeyLogic, MuteHotkeyLogic, EditorQuickSaveHotkeyLogic StopMusicKey: StopMusic PauseMusicKey: PauseMusic PrevMusicKey: PrevMusic NextMusicKey: NextMusic TakeScreenshotKey: TakeScreenshot MuteAudioKey: ToggleMute + QuickSaveKey: EditorQuickSave LogicKeyListener@WORLD_KEYHANDLER: Logic: ResetZoomHotkeyLogic ResetZoomKey: ResetZoom diff --git a/mods/common/hotkeys/editor.yaml b/mods/common/hotkeys/editor.yaml index ab43f911e9..fa737a654d 100644 --- a/mods/common/hotkeys/editor.yaml +++ b/mods/common/hotkeys/editor.yaml @@ -20,6 +20,13 @@ EditorCopy: C Ctrl Platform: OSX: C Meta +EditorQuickSave: S Ctrl + Description: Save Map + Types: Editor + Contexts: Editor + Platform: + OSX: S Meta + EditorTilesTab: E Description: Tiles Tab Types: Editor diff --git a/mods/common/languages/en.ftl b/mods/common/languages/en.ftl index 9faddc453e..978a699666 100644 --- a/mods/common/languages/en.ftl +++ b/mods/common/languages/en.ftl @@ -129,6 +129,8 @@ dialog-overwrite-map-outside-edit = By saving you may overwrite progress .confirm = Save +notification-save-current-map = Saved current map. + ## GameInfoLogic menu-game-info = .objectives = Objectives