diff --git a/OpenRA.Game/Scripting/ScriptContext.cs b/OpenRA.Game/Scripting/ScriptContext.cs index b1109229e6..7bd03817b9 100644 --- a/OpenRA.Game/Scripting/ScriptContext.cs +++ b/OpenRA.Game/Scripting/ScriptContext.cs @@ -11,6 +11,7 @@ using System; using System.Collections.Generic; using System.Drawing; +using System.Diagnostics; using System.IO; using System.Linq; using System.Reflection; @@ -118,6 +119,8 @@ namespace OpenRA.Scripting { runtime = new MemoryConstrainedLuaRuntime(); + Log.AddChannel("lua", "lua.log"); + World = world; WorldRenderer = worldRenderer; knownActorCommands = Game.modData.ObjectCreator @@ -141,7 +144,7 @@ namespace OpenRA.Scripting using (var registerGlobal = (LuaFunction)runtime.Globals["RegisterSandboxedGlobal"]) { - using (var fn = runtime.CreateFunctionFromDelegate((Action)Console.WriteLine)) + using (var fn = runtime.CreateFunctionFromDelegate((Action)LogDebugMessage)) registerGlobal.Call("print", fn).Dispose(); // Register global tables @@ -173,12 +176,30 @@ namespace OpenRA.Scripting } } - bool error; + void LogDebugMessage(string message) + { + Console.WriteLine("Lua debug: {0}", message); + Log.Write("lua", message); + } + + public bool FatalErrorOccurred { get; private set; } public void FatalError(string message) { + var stacktrace = new StackTrace().ToString(); Console.WriteLine("Fatal Lua Error: {0}", message); - Game.AddChatLine(Color.White, "Fatal Lua Error", message); - error = true; + Console.WriteLine(stacktrace); + + Log.Write("lua", "Fatal Lua Error: {0}", message); + Log.Write("lua", stacktrace); + + FatalErrorOccurred = true; + + World.AddFrameEndTask(w => + { + World.EndGame(); + World.SetPauseState(true); + World.PauseStateLocked = true; + }); } public void RegisterMapActor(string name, Actor a) @@ -195,7 +216,7 @@ namespace OpenRA.Scripting public void WorldLoaded() { - if (error) + if (FatalErrorOccurred) return; using (var worldLoaded = (LuaFunction)runtime.Globals["WorldLoaded"]) @@ -204,7 +225,7 @@ namespace OpenRA.Scripting public void Tick(Actor self) { - if (error || disposed) + if (FatalErrorOccurred || disposed) return; using (new PerfSample("tick_lua")) @@ -215,6 +236,7 @@ namespace OpenRA.Scripting { if (disposed) return; + disposed = true; if (runtime != null) runtime.Dispose(); diff --git a/OpenRA.Mods.RA/Scripting/Global/TriggerGlobal.cs b/OpenRA.Mods.RA/Scripting/Global/TriggerGlobal.cs index d67f17035e..08a2b5be90 100644 --- a/OpenRA.Mods.RA/Scripting/Global/TriggerGlobal.cs +++ b/OpenRA.Mods.RA/Scripting/Global/TriggerGlobal.cs @@ -43,7 +43,7 @@ namespace OpenRA.Mods.RA.Scripting using (f) f.Call(); } - catch (LuaException e) + catch (Exception e) { context.FatalError(e.Message); } @@ -81,11 +81,18 @@ namespace OpenRA.Mods.RA.Scripting var copy = (LuaFunction)func.CopyReference(); Action OnMemberKilled = m => { - group.Remove(m); - if (!group.Any()) + try { - copy.Call(); - copy.Dispose(); + group.Remove(m); + if (!group.Any()) + { + copy.Call(); + copy.Dispose(); + } + } + catch (Exception e) + { + context.FatalError(e.Message); } }; @@ -101,14 +108,21 @@ namespace OpenRA.Mods.RA.Scripting var copy = (LuaFunction)func.CopyReference(); Action OnMemberKilled = m => { - if (called) - return; + try + { + if (called) + return; - using (var killed = m.ToLuaValue(context)) - copy.Call(killed).Dispose(); + using (var killed = m.ToLuaValue(context)) + copy.Call(killed).Dispose(); - copy.Dispose(); - called = true; + copy.Dispose(); + called = true; + } + catch (Exception e) + { + context.FatalError(e.Message); + } }; foreach (var a in actors) @@ -180,11 +194,18 @@ namespace OpenRA.Mods.RA.Scripting var copy = (LuaFunction)func.CopyReference(); Action OnMemberRemoved = m => { - group.Remove(m); - if (!group.Any()) + try { - copy.Call().Dispose(); - copy.Dispose(); + group.Remove(m); + if (!group.Any()) + { + copy.Call().Dispose(); + copy.Dispose(); + } + } + catch (Exception e) + { + context.FatalError(e.Message); } }; @@ -208,9 +229,16 @@ namespace OpenRA.Mods.RA.Scripting var onEntry = (LuaFunction)func.CopyReference(); Action invokeEntry = a => { - using (var luaActor = a.ToLuaValue(context)) - using (var id = triggerId.ToLuaValue(context)) - onEntry.Call(luaActor, id).Dispose(); + try + { + using (var luaActor = a.ToLuaValue(context)) + using (var id = triggerId.ToLuaValue(context)) + onEntry.Call(luaActor, id).Dispose(); + } + catch (Exception e) + { + context.FatalError(e.Message); + } }; triggerId = context.World.ActorMap.AddCellTrigger(cells, invokeEntry, null); @@ -227,9 +255,16 @@ namespace OpenRA.Mods.RA.Scripting var onExit = (LuaFunction)func.CopyReference(); Action invokeExit = a => { - using (var luaActor = a.ToLuaValue(context)) - using (var id = triggerId.ToLuaValue(context)) - onExit.Call(luaActor, id).Dispose(); + try + { + using (var luaActor = a.ToLuaValue(context)) + using (var id = triggerId.ToLuaValue(context)) + onExit.Call(luaActor, id).Dispose(); + } + catch (Exception e) + { + context.FatalError(e.Message); + } }; triggerId = context.World.ActorMap.AddCellTrigger(cells, null, invokeExit); diff --git a/OpenRA.Mods.RA/Scripting/LuaScript.cs b/OpenRA.Mods.RA/Scripting/LuaScript.cs index 1a23e62235..192001913e 100644 --- a/OpenRA.Mods.RA/Scripting/LuaScript.cs +++ b/OpenRA.Mods.RA/Scripting/LuaScript.cs @@ -50,5 +50,7 @@ namespace OpenRA.Mods.RA.Scripting if (context != null) context.Dispose(); } + + public bool FatalErrorOccurred { get { return context.FatalErrorOccurred; } } } } diff --git a/OpenRA.Mods.RA/Scripting/ScriptTriggers.cs b/OpenRA.Mods.RA/Scripting/ScriptTriggers.cs index b7d226c742..aeee22ce1b 100644 --- a/OpenRA.Mods.RA/Scripting/ScriptTriggers.cs +++ b/OpenRA.Mods.RA/Scripting/ScriptTriggers.cs @@ -45,25 +45,55 @@ namespace OpenRA.Mods.RA.Scripting public void TickIdle(Actor self) { foreach (var f in Triggers[Trigger.OnIdle]) - using (var a = self.ToLuaValue(f.Second)) - f.First.Call(a).Dispose(); + { + try + { + using (var a = self.ToLuaValue(f.Second)) + f.First.Call(a).Dispose(); + } + catch (Exception ex) + { + f.Second.FatalError(ex.Message); + return; + } + } } public void Damaged(Actor self, AttackInfo e) { foreach (var f in Triggers[Trigger.OnDamaged]) - using (var a = self.ToLuaValue(f.Second)) - using (var b = e.Attacker.ToLuaValue(f.Second)) - f.First.Call(a, b).Dispose(); + { + try + { + using (var a = self.ToLuaValue(f.Second)) + using (var b = e.Attacker.ToLuaValue(f.Second)) + f.First.Call(a, b).Dispose(); + } + catch (Exception ex) + { + f.Second.FatalError(ex.Message); + return; + } + } } public void Killed(Actor self, AttackInfo e) { // Run Lua callbacks foreach (var f in Triggers[Trigger.OnKilled]) - using (var a = self.ToLuaValue(f.Second)) - using (var b = e.Attacker.ToLuaValue(f.Second)) - f.First.Call(a, b).Dispose(); + { + try + { + using (var a = self.ToLuaValue(f.Second)) + using (var b = e.Attacker.ToLuaValue(f.Second)) + f.First.Call(a, b).Dispose(); + } + catch (Exception ex) + { + f.Second.FatalError(ex.Message); + return; + } + } // Run any internally bound callbacks OnKilledInternal(self); @@ -72,72 +102,162 @@ namespace OpenRA.Mods.RA.Scripting public void UnitProduced(Actor self, Actor other, CPos exit) { foreach (var f in Triggers[Trigger.OnProduction]) - using (var a = self.ToLuaValue(f.Second)) - using (var b = other.ToLuaValue(f.Second)) - f.First.Call(a, b).Dispose(); + { + try + { + using (var a = self.ToLuaValue(f.Second)) + using (var b = other.ToLuaValue(f.Second)) + f.First.Call(a, b).Dispose(); + } + catch (Exception ex) + { + f.Second.FatalError(ex.Message); + return; + } + } } public void OnPlayerWon(Player player) { foreach (var f in Triggers[Trigger.OnPlayerWon]) - using (var a = player.ToLuaValue(f.Second)) - f.First.Call(a).Dispose(); + { + try + { + using (var a = player.ToLuaValue(f.Second)) + f.First.Call(a).Dispose(); + } + catch (Exception ex) + { + f.Second.FatalError(ex.Message); + return; + } + } } public void OnPlayerLost(Player player) { foreach (var f in Triggers[Trigger.OnPlayerLost]) - using (var a = player.ToLuaValue(f.Second)) - f.First.Call(a).Dispose(); + { + try + { + using (var a = player.ToLuaValue(f.Second)) + f.First.Call(a).Dispose(); + } + catch (Exception ex) + { + f.Second.FatalError(ex.Message); + return; + } + } } public void OnObjectiveAdded(Player player, int id) { foreach (var f in Triggers[Trigger.OnObjectiveAdded]) - using (var a = player.ToLuaValue(f.Second)) - using (var b = id.ToLuaValue(f.Second)) - f.First.Call(a, b).Dispose(); + { + try + { + using (var a = player.ToLuaValue(f.Second)) + using (var b = id.ToLuaValue(f.Second)) + f.First.Call(a, b).Dispose(); + } + catch (Exception ex) + { + f.Second.FatalError(ex.Message); + return; + } + } } public void OnObjectiveCompleted(Player player, int id) { foreach (var f in Triggers[Trigger.OnObjectiveCompleted]) - using (var a = player.ToLuaValue(f.Second)) - using (var b = id.ToLuaValue(f.Second)) - f.First.Call(a, b).Dispose(); + { + try + { + using (var a = player.ToLuaValue(f.Second)) + using (var b = id.ToLuaValue(f.Second)) + f.First.Call(a, b).Dispose(); + } + catch (Exception ex) + { + f.Second.FatalError(ex.Message); + return; + } + } } public void OnObjectiveFailed(Player player, int id) { foreach (var f in Triggers[Trigger.OnObjectiveFailed]) - using (var a = player.ToLuaValue(f.Second)) - using (var b = id.ToLuaValue(f.Second)) - f.First.Call(a, b).Dispose(); + { + try + { + using (var a = player.ToLuaValue(f.Second)) + using (var b = id.ToLuaValue(f.Second)) + f.First.Call(a, b).Dispose(); + } + catch (Exception ex) + { + f.Second.FatalError(ex.Message); + return; + } + } } public void OnCapture(Actor self, Actor captor, Player oldOwner, Player newOwner) { foreach (var f in Triggers[Trigger.OnCapture]) - using (var a = self.ToLuaValue(f.Second)) - using (var b = captor.ToLuaValue(f.Second)) - using (var c = oldOwner.ToLuaValue(f.Second)) - using (var d = newOwner.ToLuaValue(f.Second)) - f.First.Call(a, b, c, d).Dispose(); + { + try + { + using (var a = self.ToLuaValue(f.Second)) + using (var b = captor.ToLuaValue(f.Second)) + using (var c = oldOwner.ToLuaValue(f.Second)) + using (var d = newOwner.ToLuaValue(f.Second)) + f.First.Call(a, b, c, d).Dispose(); + } + catch (Exception ex) + { + f.Second.FatalError(ex.Message); + return; + } + } } public void AddedToWorld(Actor self) { foreach (var f in Triggers[Trigger.OnAddedToWorld]) - using (var a = self.ToLuaValue(f.Second)) - f.First.Call(a).Dispose(); + { + try + { + using (var a = self.ToLuaValue(f.Second)) + f.First.Call(a).Dispose(); + } + catch (Exception ex) + { + f.Second.FatalError(ex.Message); + return; + } + } } public void RemovedFromWorld(Actor self) { // Run Lua callbacks foreach (var f in Triggers[Trigger.OnRemovedFromWorld]) - using (var a = self.ToLuaValue(f.Second)) - f.First.Call(a).Dispose(); + { + try + { + using (var a = self.ToLuaValue(f.Second)) + f.First.Call(a).Dispose(); + } + catch (Exception ex) + { + f.Second.FatalError(ex.Message); + return; + } + } // Run any internally bound callbacks OnRemovedInternal(self); diff --git a/OpenRA.Mods.RA/Widgets/Logic/Ingame/LeaveMapLogic.cs b/OpenRA.Mods.RA/Widgets/Logic/Ingame/LeaveMapLogic.cs index e6a76f6f95..6236eeb038 100644 --- a/OpenRA.Mods.RA/Widgets/Logic/Ingame/LeaveMapLogic.cs +++ b/OpenRA.Mods.RA/Widgets/Logic/Ingame/LeaveMapLogic.cs @@ -11,6 +11,7 @@ using System; using System.Drawing; using System.Linq; +using OpenRA.Mods.RA.Scripting; using OpenRA.Network; using OpenRA.Traits; using OpenRA.Widgets; @@ -34,9 +35,11 @@ namespace OpenRA.Mods.RA.Widgets var showStats = false; + var scriptContext = world.WorldActor.TraitOrDefault(); var iop = world.WorldActor.TraitsImplementing().FirstOrDefault(); var isMultiplayer = !world.LobbyInfo.IsSinglePlayer && !world.IsReplay; - var hasObjectives = iop != null && iop.PanelName != null && world.LocalPlayer != null; + var hasError = scriptContext != null && scriptContext.FatalErrorOccurred; + var hasObjectives = hasError || (iop != null && iop.PanelName != null && world.LocalPlayer != null); var showTabs = hasObjectives && isMultiplayer; currentTab = hasObjectives ? Tab.Objectives : Tab.Chat; @@ -114,8 +117,9 @@ namespace OpenRA.Mods.RA.Widgets if (hasObjectives) { + var panel = hasError ? "SCRIPT_ERROR_PANEL" : iop.PanelName; var objectivesContainer = dialog.Get("OBJECTIVES_PANEL"); - Game.LoadWidget(world, iop.PanelName, objectivesContainer, new WidgetArgs()); + Game.LoadWidget(world, panel, objectivesContainer, new WidgetArgs()); objectivesContainer.IsVisible = () => currentTab == Tab.Objectives; } diff --git a/mods/cnc/chrome/ingame-infoscripterror.yaml b/mods/cnc/chrome/ingame-infoscripterror.yaml new file mode 100644 index 0000000000..656a2f7c49 --- /dev/null +++ b/mods/cnc/chrome/ingame-infoscripterror.yaml @@ -0,0 +1,27 @@ +Container@SCRIPT_ERROR_PANEL: + Height: PARENT_BOTTOM + Width: PARENT_RIGHT + Children: + Label@DESCA: + X: 15 + Y: 15 + Width: PARENT_RIGHT-30 + Height: 20 + Font: Bold + Align:Center + Text: The map script has encountered a fatal error + Label@DESCB: + X: 15 + Y: 45 + Width: PARENT_RIGHT-30 + Height: 20 + Font: Regular + WordWrap: true + Text: The details of the error have been saved to lua.log in the logs directory. + Label@DESCC: + X: 15 + Y: 65 + Width: PARENT_RIGHT-30 + Height: 20 + Font: Regular + Text: Please send this file to the map author so that they can fix this issue. \ No newline at end of file diff --git a/mods/cnc/chrome/ingame-leavemap.yaml b/mods/cnc/chrome/ingame-leavemap.yaml index 948f8758f7..9484644156 100644 --- a/mods/cnc/chrome/ingame-leavemap.yaml +++ b/mods/cnc/chrome/ingame-leavemap.yaml @@ -116,6 +116,8 @@ Container@LEAVE_MAP_WIDGET: Font: Bold Text: Statistics Container@OBJECTIVES_PANEL: + Width: PARENT_RIGHT + Height: 365 Container@DIALOG_CHAT_PANEL: X: 15 Y: 15 diff --git a/mods/cnc/mod.yaml b/mods/cnc/mod.yaml index 414badb611..c7d0354c08 100644 --- a/mods/cnc/mod.yaml +++ b/mods/cnc/mod.yaml @@ -93,6 +93,7 @@ ChromeLayout: ./mods/cnc/chrome/ingame-debug.yaml ./mods/cnc/chrome/ingame-info.yaml ./mods/cnc/chrome/ingame-infobriefing.yaml + ./mods/cnc/chrome/ingame-infoscripterror.yaml ./mods/cnc/chrome/ingame-infoobjectives.yaml ./mods/cnc/chrome/ingame-infostats.yaml ./mods/cnc/chrome/ingame-leavemap.yaml diff --git a/mods/d2k/mod.yaml b/mods/d2k/mod.yaml index d014c63ede..293fd183e8 100644 --- a/mods/d2k/mod.yaml +++ b/mods/d2k/mod.yaml @@ -64,6 +64,7 @@ ChromeLayout: ./mods/ra/chrome/ingame-fmvplayer.yaml ./mods/d2k/chrome/ingame-menu.yaml ./mods/ra/chrome/ingame-info.yaml + ./mods/ra/chrome/ingame-infoscripterror.yaml ./mods/ra/chrome/ingame-infobriefing.yaml ./mods/ra/chrome/ingame-infoobjectives.yaml ./mods/ra/chrome/ingame-infostats.yaml diff --git a/mods/ra/chrome/ingame-infoscripterror.yaml b/mods/ra/chrome/ingame-infoscripterror.yaml new file mode 100644 index 0000000000..656a2f7c49 --- /dev/null +++ b/mods/ra/chrome/ingame-infoscripterror.yaml @@ -0,0 +1,27 @@ +Container@SCRIPT_ERROR_PANEL: + Height: PARENT_BOTTOM + Width: PARENT_RIGHT + Children: + Label@DESCA: + X: 15 + Y: 15 + Width: PARENT_RIGHT-30 + Height: 20 + Font: Bold + Align:Center + Text: The map script has encountered a fatal error + Label@DESCB: + X: 15 + Y: 45 + Width: PARENT_RIGHT-30 + Height: 20 + Font: Regular + WordWrap: true + Text: The details of the error have been saved to lua.log in the logs directory. + Label@DESCC: + X: 15 + Y: 65 + Width: PARENT_RIGHT-30 + Height: 20 + Font: Regular + Text: Please send this file to the map author so that they can fix this issue. \ No newline at end of file diff --git a/mods/ra/chrome/ingame-leavemap.yaml b/mods/ra/chrome/ingame-leavemap.yaml index c85288b804..10aca4d920 100644 --- a/mods/ra/chrome/ingame-leavemap.yaml +++ b/mods/ra/chrome/ingame-leavemap.yaml @@ -126,6 +126,7 @@ Container@LEAVE_MAP_WIDGET: Text: Leave Container@OBJECTIVES_PANEL: Y: 65 + Width: PARENT_RIGHT Container@DIALOG_CHAT_PANEL: X: 20 Y: 65 diff --git a/mods/ra/mod.yaml b/mods/ra/mod.yaml index b1da6da59d..1231ab89f2 100644 --- a/mods/ra/mod.yaml +++ b/mods/ra/mod.yaml @@ -76,6 +76,7 @@ ChromeLayout: ./mods/ra/chrome/ingame-diplomacy.yaml ./mods/ra/chrome/ingame-fmvplayer.yaml ./mods/ra/chrome/ingame-info.yaml + ./mods/ra/chrome/ingame-infoscripterror.yaml ./mods/ra/chrome/ingame-infobriefing.yaml ./mods/ra/chrome/ingame-infoobjectives.yaml ./mods/ra/chrome/ingame-infostats.yaml diff --git a/mods/ts/mod.yaml b/mods/ts/mod.yaml index f8b385548e..cc905d1d50 100644 --- a/mods/ts/mod.yaml +++ b/mods/ts/mod.yaml @@ -106,6 +106,7 @@ ChromeLayout: ./mods/ra/chrome/ingame-fmvplayer.yaml ./mods/ra/chrome/ingame-menu.yaml ./mods/ra/chrome/ingame-info.yaml + ./mods/ra/chrome/ingame-infoscripterror.yaml ./mods/ra/chrome/ingame-infobriefing.yaml ./mods/ra/chrome/ingame-infoobjectives.yaml ./mods/ra/chrome/ingame-infostats.yaml