diff --git a/Common/ac/game_version.h b/Common/ac/game_version.h index 72553b5e04a..4b8d3f55ee5 100644 --- a/Common/ac/game_version.h +++ b/Common/ac/game_version.h @@ -114,6 +114,8 @@ Idle animation speed, modifiable hotspot names, fixed video frame Some adjustments to gui text alignment. 3.6.1: In RTL mode all text is reversed, not only wrappable (labels etc). +3.6.1.10: +Disabled automatic SetRestartPoint. */ @@ -153,7 +155,8 @@ enum GameDataVersion kGameVersion_360_16 = 3060016, kGameVersion_360_21 = 3060021, kGameVersion_361 = 3060100, - kGameVersion_Current = kGameVersion_361 + kGameVersion_361_10 = 3060110, + kGameVersion_Current = kGameVersion_361_10 }; // Data format version of the loaded game diff --git a/Editor/AGS.Editor/AGSEditor.cs b/Editor/AGS.Editor/AGSEditor.cs index c2cec7413db..af7f80fea43 100644 --- a/Editor/AGS.Editor/AGSEditor.cs +++ b/Editor/AGS.Editor/AGSEditor.cs @@ -102,8 +102,10 @@ public class AGSEditor * 3.6.1.2 - GUIListBox.Translated property moved to GUIControl parent * 3.6.1.3 - RuntimeSetup.TextureCache, SoundCache * 3.6.1.9 - Settings.ScaleCharacterSpriteOffsets + * 3.6.1.10 - SetRestartPoint() is no longer auto called in the engine, + * add one into the global script when importing older games. */ - public const int LATEST_XML_VERSION_INDEX = 3060109; + public const int LATEST_XML_VERSION_INDEX = 3060110; /* * LATEST_USER_DATA_VERSION is the last version of the user data file that used a * 4-point-4-number string to identify the version of AGS that saved the file. @@ -873,13 +875,16 @@ private Script CompileDialogs(CompileMessages errors, bool rebuildAll) DialogScriptConverter dialogConverter = new DialogScriptConverter(); string dialogScriptsText = dialogConverter.ConvertGameDialogScripts(_game, errors, rebuildAll); Script dialogScripts = new Script(Script.DIALOG_SCRIPTS_FILE_NAME, dialogScriptsText, false); - Script globalScript = _game.RootScriptFolder.GetScriptByFileName(Script.GLOBAL_SCRIPT_FILE_NAME, true); - if (!System.Text.RegularExpressions.Regex.IsMatch(globalScript.Text, @"function\s+dialog_request\s*\(")) + + // A dialog_request must exist in the global script, otherwise + // the dialogs script fails to load at run-time + // TODO: check if it's still true, and is necessary! + Script script = CurrentGame.RootScriptFolder.GetScriptByFileName(Script.GLOBAL_SCRIPT_FILE_NAME, true); + if (script != null) { - // A dialog_request must exist in the global script, otherwise - // the dialogs script fails to load at run-time - globalScript.Text += Environment.NewLine + "function dialog_request(int param) {" + Environment.NewLine + "}"; + script.Text = ScriptGeneration.InsertFunction(script.Text, "dialog_request", "int param"); } + return dialogScripts; } diff --git a/Editor/AGS.Editor/AGSEditor.csproj b/Editor/AGS.Editor/AGSEditor.csproj index 09f0155c6da..eca03473469 100644 --- a/Editor/AGS.Editor/AGSEditor.csproj +++ b/Editor/AGS.Editor/AGSEditor.csproj @@ -375,6 +375,7 @@ + Designer diff --git a/Editor/AGS.Editor/Components/RoomsComponent.cs b/Editor/AGS.Editor/Components/RoomsComponent.cs index 50d878396c0..21148a7d520 100644 --- a/Editor/AGS.Editor/Components/RoomsComponent.cs +++ b/Editor/AGS.Editor/Components/RoomsComponent.cs @@ -859,32 +859,17 @@ private bool AddPlayMusicCommandToPlayerEntersRoomScript(Room room, CompileMessa return scriptModified; } - string scriptName = room.Interactions.GetScriptFunctionNameForInteractionSuffix(Room.EVENT_SUFFIX_ROOM_LOAD); - if (string.IsNullOrEmpty(scriptName)) + string functionName = room.Interactions.GetScriptFunctionNameForInteractionSuffix(Room.EVENT_SUFFIX_ROOM_LOAD); + if (string.IsNullOrEmpty(functionName)) { - scriptName = "Room_" + Room.EVENT_SUFFIX_ROOM_LOAD; - room.Interactions.SetScriptFunctionNameForInteractionSuffix(Room.EVENT_SUFFIX_ROOM_LOAD, scriptName); - room.Script.Text += Environment.NewLine + "function " + scriptName + "()" + - Environment.NewLine + "{" + Environment.NewLine + - "}" + Environment.NewLine; - scriptModified = true; - } - int functionOffset = room.Script.Text.IndexOf(scriptName); - if (functionOffset < 0) - { - errors.Add(new CompileWarning("Room " + room.Number + ": Unable to find definition for " + scriptName + " to add Room Load music " + room.PlayMusicOnRoomLoad)); - } - else - { - functionOffset = room.Script.Text.IndexOf('{', functionOffset); - functionOffset = room.Script.Text.IndexOf('\n', functionOffset) + 1; - string newScript = room.Script.Text.Substring(0, functionOffset); - newScript += " " + clip.ScriptName + ".Play();" + Environment.NewLine; - newScript += room.Script.Text.Substring(functionOffset); - room.Script.Text = newScript; - room.PlayMusicOnRoomLoad = 0; - scriptModified = true; + functionName = "Room_" + Room.EVENT_SUFFIX_ROOM_LOAD; + room.Interactions.SetScriptFunctionNameForInteractionSuffix(Room.EVENT_SUFFIX_ROOM_LOAD, functionName); } + + room.Script.Text = ScriptGeneration.InsertFunction(room.Script.Text, functionName, "", + " " + clip.ScriptName + ".Play();", amendExisting: true, insertBeforeExistingCode: true); + room.PlayMusicOnRoomLoad = 0; + scriptModified = true; } return scriptModified; diff --git a/Editor/AGS.Editor/Components/ScriptsComponent.cs b/Editor/AGS.Editor/Components/ScriptsComponent.cs index 5d3ecfea5a1..a4132991b22 100644 --- a/Editor/AGS.Editor/Components/ScriptsComponent.cs +++ b/Editor/AGS.Editor/Components/ScriptsComponent.cs @@ -76,6 +76,8 @@ public ScriptsComponent(GUIController guiController, AGSEditor agsEditor) _guiController.OnScriptChanged += new GUIController.ScriptChangedHandler(GUIController_OnScriptChanged); _guiController.OnGetScriptEditorControl += new GUIController.GetScriptEditorControlHandler(_guiController_OnGetScriptEditorControl); _guiController.ProjectTree.OnAfterLabelEdit += new ProjectTree.AfterLabelEditHandler(ProjectTree_OnAfterLabelEdit); + + Factory.Events.GamePostLoad += Events_GamePostLoad; } private void _guiController_OnGetScriptEditorControl(GetScriptEditorControlEventArgs evArgs) @@ -630,5 +632,26 @@ protected override IList GetFlatList() { return null; } + + private void Events_GamePostLoad() + { + var game = _agsEditor.CurrentGame; + if (game.SavedXmlVersionIndex >= 3060110) + return; // no upgrade necessary + + // < 3060110 - SetRestartPoint() has to be added to Global Script's game_start, + // emulate legacy behavior where its call was hardcoded in the engine. + if (game.SavedXmlVersionIndex < 3060110) + { + Script script = AGSEditor.Instance.CurrentGame.RootScriptFolder.GetScriptByFileName(Script.GLOBAL_SCRIPT_FILE_NAME, true); + if (script != null) + { + script.Text = + ScriptGeneration.InsertFunction(script.Text, "game_start", "", " SetRestartPoint();", amendExisting: true); + // CHECKME: do not save the script here, in case user made a mistake opening this in a newer editor + // and closes project without saving after upgrade? Upgrade process is not well defined... + } + } + } } } diff --git a/Editor/AGS.Editor/GUI/GUIController.cs b/Editor/AGS.Editor/GUI/GUIController.cs index 47c55291c83..c21063215f3 100644 --- a/Editor/AGS.Editor/GUI/GUIController.cs +++ b/Editor/AGS.Editor/GUI/GUIController.cs @@ -1296,18 +1296,11 @@ private void ScriptFunctionUIEditor_CreateScriptFunction(bool isGlobalScript, st OnGetScript(scriptToRetrieve, ref script); if (script != null) { - string functionStart = "function " + functionName + "("; - if (script.Text.IndexOf(functionStart) < 0) + if (_agsEditor.AttemptToGetWriteAccess(script.FileName)) { - if (_agsEditor.AttemptToGetWriteAccess(script.FileName)) - { - script.Text += Environment.NewLine + functionStart + parameters + ")" + Environment.NewLine; - script.Text += "{" + Environment.NewLine + Environment.NewLine + "}" + Environment.NewLine; - if (OnScriptChanged != null) - { - OnScriptChanged(script); - } - } + script.Text = ScriptGeneration.InsertFunction(script.Text, functionName, parameters); + if (script.Modified) + OnScriptChanged?.Invoke(script); } } } diff --git a/Editor/AGS.Editor/Utils/ScriptGeneration.cs b/Editor/AGS.Editor/Utils/ScriptGeneration.cs new file mode 100644 index 00000000000..3e357a21cd1 --- /dev/null +++ b/Editor/AGS.Editor/Utils/ScriptGeneration.cs @@ -0,0 +1,98 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; + +namespace AGS.Editor +{ + /// + /// Helper class ScriptGeneration provides methods for automatic script editing. + /// + public class ScriptGeneration + { + /// + /// Counts braces starting with the startIndex, and finds the matching closing one. + /// Returns an index of a closing brace. + /// + public static int FindClosingBrace(string text, int startIndex, char braceOpenChar = '{', char braceCloseChar = '}') + { + // TODO: is it possible to do this using regex? + int count = 0; + int closeBraceAt = -1; + for (int i = startIndex; i < text.Length; ++i) + { + if (text[i] == braceOpenChar) + { + count++; + } + else if (text[i] == braceCloseChar) + { + count--; + if (count == 0) + { + closeBraceAt = i; + break; + } + } + } + return closeBraceAt; + } + + /// + /// Appends a function with optional parameter list and optional contents to a + /// script's text. + /// + /// Tells whether to amend the function code in existing + /// function, or only set it if the new function is created. + /// Resulting script's text. + public static string InsertFunction(string text, string functionName, string paramList = "", string functionCode = "", + bool amendExisting = false, bool insertBeforeExistingCode = false) + { + // TODO: support matching indentation for the new code? + + // NOTE: we must find a function with opening brace, because there may also + // be a function prototype somewhere. + var match = Regex.Match(text, string.Format(@"function\s+{0}\s*\(.*\)\s*{{", functionName)); + if (match.Success && (!amendExisting || string.IsNullOrWhiteSpace(functionCode))) + return text; // function already exists and don't have to amend + if (match.Success) + { + // Find the required position in the existing function, and insert required code + int functionStart = match.Index + match.Length; + if (insertBeforeExistingCode) + { + text = string.Format("{0}{1}{2}{3}{4}", + text.Substring(0, functionStart), + Environment.NewLine, functionCode, Environment.NewLine, + text.Substring(functionStart)); + } + else + { + int closeBraceAt = FindClosingBrace(text, match.Index); + if (closeBraceAt >= 0) + { + text = string.Format("{0}{1}{2}{3}{4}", + text.Substring(0, closeBraceAt), + Environment.NewLine, functionCode, Environment.NewLine, + text.Substring(closeBraceAt)); + } + else + { + // Script missing closing brace? + text = string.Format("{0}{1}{2}{3}}}", + text, Environment.NewLine, functionCode, Environment.NewLine); + } + } + } + else + { + // Add a new function to the end of the script + text += string.Format("{0}function {1}({2}){3}{{{4}{5}{6}}}{7}", + Environment.NewLine, functionName, paramList, Environment.NewLine, Environment.NewLine, + functionCode, Environment.NewLine, Environment.NewLine); + } + return text; + } + } +} diff --git a/Editor/AGS.Types/Script.cs b/Editor/AGS.Types/Script.cs index b6076013aaf..798f3a211c3 100644 --- a/Editor/AGS.Types/Script.cs +++ b/Editor/AGS.Types/Script.cs @@ -67,7 +67,14 @@ public Script(string fileName, string text, string name, string description, str public string Text { get { return _text; } - set { _text = value ?? string.Empty; _modified = true; } + set + { + if (_text != value) + { + _text = value ?? string.Empty; + _modified = true; + } + } } [ReadOnly(true)] diff --git a/Engine/main/game_start.cpp b/Engine/main/game_start.cpp index 9462ce64cee..28287ee253b 100644 --- a/Engine/main/game_start.cpp +++ b/Engine/main/game_start.cpp @@ -72,7 +72,10 @@ void start_game() { our_eip = -43; - SetRestartPoint(); + // Only auto-set first restart point in < 3.6.1 games, + // since 3.6.1+ users are suggested to set one manually in script. + if (loaded_game_file_version < kGameVersion_361_10) + SetRestartPoint(); our_eip=-3;