diff --git a/Rendering/TheEngine/IGame.cs b/Rendering/TheEngine/IGame.cs index 4293e408a..aac7f0575 100644 --- a/Rendering/TheEngine/IGame.cs +++ b/Rendering/TheEngine/IGame.cs @@ -2,7 +2,7 @@ namespace TheEngine { public interface IGame { - public void Initialize(Engine engine); + public bool Initialize(Engine engine); public void Update(float diff); public void Render(float delta); public event Action RequestDispose; diff --git a/Rendering/TheEngine/Managers/MeshManager.cs b/Rendering/TheEngine/Managers/MeshManager.cs index 8e2dc4de2..ea7022761 100644 --- a/Rendering/TheEngine/Managers/MeshManager.cs +++ b/Rendering/TheEngine/Managers/MeshManager.cs @@ -91,6 +91,7 @@ public IMesh CreateManagedOnlyMesh(Vector3[] vertices, int[] indices) var mesh = new Mesh(engine, handle, true); mesh.SetVertices(vertices); mesh.SetIndices(indices, 0); + mesh.BuildBoundingBox(); meshes.Add(mesh); #if TRACK_ALLOCATIONS diff --git a/Rendering/TheEngine/TheEnginePanel.cs b/Rendering/TheEngine/TheEnginePanel.cs index 481ea7cde..52b031273 100644 --- a/Rendering/TheEngine/TheEnginePanel.cs +++ b/Rendering/TheEngine/TheEnginePanel.cs @@ -140,6 +140,9 @@ protected override void OnOpenGlDeinit(GlInterface gl, int fb) protected override void OnOpenGlRender(GlInterface gl, int fb) { + if (engine == null || delayedDispose) + return; + engine.statsManager.PixelSize = new Vector2(PixelSize.Item1, PixelSize.Item2); engine.statsManager.Counters.PresentTime.Add(PresentTime); renderStopwatch.Restart(); @@ -194,12 +197,14 @@ protected override void OnOpenGlRender(GlInterface gl, int fb) private bool gameInitialized; public static readonly DirectProperty GameProperty = AvaloniaProperty.RegisterDirect(nameof(Game), o => o.Game, (o, v) => o.Game = v); + private System.IDisposable? globalKeyDownDisposable; + private System.IDisposable? globalKeyUpDisposable; protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e) { base.OnAttachedToVisualTree(e); sw.Restart(); - ((IControl)e.Root).AddDisposableHandler(KeyDownEvent, GlobalKeyDown, RoutingStrategies.Tunnel); - ((IControl)e.Root).AddDisposableHandler(KeyUpEvent, GlobalKeyUp, RoutingStrategies.Tunnel); + globalKeyDownDisposable = ((IControl)e.Root).AddDisposableHandler(KeyDownEvent, GlobalKeyDown, RoutingStrategies.Tunnel); + globalKeyUpDisposable = ((IControl)e.Root).AddDisposableHandler(KeyUpEvent, GlobalKeyUp, RoutingStrategies.Tunnel); } private bool IsModifierKey(Key key) => key is Key.LeftShift or Key.LeftCtrl or Key.LeftAlt or Key.LWin; @@ -221,6 +226,10 @@ protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e //if (delayedDispose) // base.OnDetachedFromVisualTree(e); //delayedDispose = false; + globalKeyUpDisposable?.Dispose(); + globalKeyDownDisposable?.Dispose(); + globalKeyUpDisposable = null; + globalKeyDownDisposable = null; sw.Stop(); } @@ -230,7 +239,11 @@ protected virtual void Update(float delta) { gameInitialized = true; game.RequestDispose += GameOnRequestDispose; - game.Initialize(engine!); + if (!game.Initialize(engine!)) + { + GameOnRequestDispose(); + game = null; + } } game?.Update(delta); } diff --git a/Rendering/TheMaths/Vector3.cs b/Rendering/TheMaths/Vector3.cs index 12ac9de38..dfb7d1e7c 100644 --- a/Rendering/TheMaths/Vector3.cs +++ b/Rendering/TheMaths/Vector3.cs @@ -1922,5 +1922,9 @@ public override bool Equals(object value) // { // return *(Vector3*)&value; // } + public Vector3 WithY(float y) + { + return new Vector3(X, y, Z); + } } } diff --git a/WDE.MPQ/Services/MpqService.cs b/WDE.MPQ/Services/MpqService.cs index e8ad6906f..6b93e530c 100644 --- a/WDE.MPQ/Services/MpqService.cs +++ b/WDE.MPQ/Services/MpqService.cs @@ -28,7 +28,20 @@ public IMpqArchive Open() if (settings.Path == null) return new EmptyMpqArchive(); var files = Directory.EnumerateFiles(Path.Join(settings.Path, "Data"), "*.mpq", SearchOption.AllDirectories).ToList(); - return new MpqArchiveSet(files.Select(MpqArchive.Open).ToArray()); + List archives = new(); + foreach (var file in files) + { + try + { + var archive = MpqArchive.Open(file); + archives.Add(archive); + } + catch (Exception e) + { + throw new Exception("Couldn't open MPQ file " + Path.GetFileName(file) + ": " + e.Message, e); + } + } + return new MpqArchiveSet(archives.ToArray()); } private class EmptyMpqArchive : IMpqArchive diff --git a/WDE.MapRenderer/GameManager.cs b/WDE.MapRenderer/GameManager.cs index 0e620bc2c..9f97d806b 100644 --- a/WDE.MapRenderer/GameManager.cs +++ b/WDE.MapRenderer/GameManager.cs @@ -8,32 +8,68 @@ using TheEngine.PhysicsSystem; using WDE.Common.DBC; using WDE.Common.MPQ; +using WDE.Common.Services.MessageBox; using WDE.MapRenderer.Managers; +using WDE.Module.Attributes; using WDE.MpqReader; using WDE.MpqReader.Structures; namespace WDE.MapRenderer { + [AutoRegister] public class GameManager : IGame, IGameContext { - private IMpqArchive mpq; + private readonly IMpqService mpqService; private readonly IGameView gameView; + private readonly IMessageBoxService messageBoxService; private readonly IDatabaseClientFileOpener databaseClientFileOpener; private AsyncMonitor monitor = new AsyncMonitor(); private Engine engine; public event Action? OnInitialized; + public event Action? OnFailedInitialize; - public GameManager(IMpqArchive mpq, IGameView gameView, IDatabaseClientFileOpener databaseClientFileOpener) + public GameManager(IMpqService mpqService, + IGameView gameView, + IMessageBoxService messageBoxService, + IDatabaseClientFileOpener databaseClientFileOpener) { - this.mpq = mpq; + this.mpqService = mpqService; this.gameView = gameView; + this.messageBoxService = messageBoxService; this.databaseClientFileOpener = databaseClientFileOpener; UpdateLoop = new UpdateManager(this); } - public void Initialize(Engine engine) + private bool TryOpenMpq(out IMpqArchive m) + { + try + { + m = mpqService.Open(); + return true; + } + catch (Exception e) + { + messageBoxService.ShowDialog(new MessageBoxFactory() + .SetTitle("Invalid MPQ") + .SetMainInstruction("Couldn't parse game MPQ.") + .SetContent(e.Message + "\n\nAre you using modified game files?") + .WithButton("Ok", false) + .Build()); + m = null; + return false; + } + } + + public bool Initialize(Engine engine) { this.engine = engine; + if (!TryOpenMpq(out mpq)) + { + OnFailedInitialize?.Invoke(); + waitForInitialized.SetResult(false); + waitForInitialized = new(); + return false; + } coroutineManager = new(); TimeManager = new TimeManager(this); ScreenSpaceSelector = new ScreenSpaceSelector(this); @@ -51,7 +87,8 @@ public void Initialize(Engine engine) OnInitialized?.Invoke(); IsInitialized = true; - waitForInitialized.SetResult(); + waitForInitialized.SetResult(true); + return true; } public void StartCoroutine(IEnumerator coroutine) @@ -59,8 +96,8 @@ public void StartCoroutine(IEnumerator coroutine) coroutineManager.Start(coroutine); } - private TaskCompletionSource waitForInitialized = new(); - public Task WaitForInitialized => waitForInitialized.Task; + private TaskCompletionSource waitForInitialized = new(); + public Task WaitForInitialized => waitForInitialized.Task; private Material? prevMaterial; public void Update(float delta) @@ -108,7 +145,8 @@ public void SetMap(int mapId) public void DoDispose() { - RequestDispose?.Invoke(); + if (IsInitialized) + RequestDispose?.Invoke(); Debug.Assert(!IsInitialized); } @@ -125,6 +163,8 @@ public void DisposeGame() MdxManager.Dispose(); TextureManager.Dispose(); MeshManager.Dispose(); + mpq.Dispose(); + mpq = null!; coroutineManager = null!; TimeManager = null!; ScreenSpaceSelector = null!; @@ -143,6 +183,7 @@ public void DisposeGame() public Engine Engine => engine; + private IMpqArchive mpq; private CoroutineManager coroutineManager; public TimeManager TimeManager { get; private set; } public ScreenSpaceSelector ScreenSpaceSelector { get; private set; } diff --git a/WDE.MapRenderer/GameView.cs b/WDE.MapRenderer/GameView.cs index 5792e59d1..39026c601 100644 --- a/WDE.MapRenderer/GameView.cs +++ b/WDE.MapRenderer/GameView.cs @@ -45,12 +45,4 @@ public async Task Open() return tool.Game; } } - - public class GemView : OpenGlControlBase - { - protected override void OnOpenGlRender(GlInterface gl, int fb) - { - - } - } } \ No newline at end of file diff --git a/WDE.MapRenderer/GameViewModel.cs b/WDE.MapRenderer/GameViewModel.cs index f4d8d553d..ed846d126 100644 --- a/WDE.MapRenderer/GameViewModel.cs +++ b/WDE.MapRenderer/GameViewModel.cs @@ -90,13 +90,18 @@ public GameViewModel(IMpqService mpqService, IMessageBoxService messageBoxService, IDatabaseClientFileOpener databaseClientFileOpener, IGameView gameView, + GameManager gameManager, GameViewSettings settings) { this.mpqService = mpqService; this.messageBoxService = messageBoxService; this.settings = settings; MapData = mapData; - Game = new GameManager(mpqService.Open(), gameView, databaseClientFileOpener); + Game = gameManager; + Game.OnFailedInitialize += () => + { + Dispatcher.UIThread.Post(() => Visibility = false, DispatcherPriority.Background); + }; Game.OnInitialized += () => { Game.LightingManager.OverrideLighting = settings.OverrideLighting; diff --git a/WDE.MapRenderer/Managers/IGameContext.cs b/WDE.MapRenderer/Managers/IGameContext.cs index 76e0fbf8c..5fc2119e1 100644 --- a/WDE.MapRenderer/Managers/IGameContext.cs +++ b/WDE.MapRenderer/Managers/IGameContext.cs @@ -28,6 +28,6 @@ public interface IGameContext Map CurrentMap { get; } void SetMap(int id); void StartCoroutine(IEnumerator coroutine); - Task WaitForInitialized { get; } + Task WaitForInitialized { get; } } } \ No newline at end of file diff --git a/WDE.MapRenderer/Managers/MdxManager.cs b/WDE.MapRenderer/Managers/MdxManager.cs index 9b7901114..3cb5d4b78 100644 --- a/WDE.MapRenderer/Managers/MdxManager.cs +++ b/WDE.MapRenderer/Managers/MdxManager.cs @@ -204,14 +204,18 @@ public IEnumerator LoadM2Mesh(string path, TaskCompletionSource re material.SetTexture("texture2", th2 ?? gameContext.TextureManager.EmptyTexture); var trans = 1.0f; - if (batch.colorIndex != -1) + if (batch.colorIndex != -1 && m2.colors.Length < batch.colorIndex) { - trans = m2.colors[batch.colorIndex].alpha.values[0][0].Value; + if (m2.colors[batch.colorIndex].alpha.values.Length == 0 || m2.colors[batch.colorIndex].alpha.values[0].Length == 0) + trans = 1; + else + trans = m2.colors[batch.colorIndex].alpha.values[0][0].Value; } - if (batch.transparencyIndex != -1) + if (batch.transparencyIndex != -1 && m2.texture_weights.Length < batch.transparencyIndex) { - trans *= m2.texture_weights[batch.transparencyIndex].weight.values[0][0].Value; + if (m2.texture_weights[batch.transparencyIndex].weight.values.Length > 0 && m2.texture_weights[batch.transparencyIndex].weight.values[0].Length > 0) + trans *= m2.texture_weights[batch.transparencyIndex].weight.values[0][0].Value; } Vector4 mesh_color = new Vector4(1.0f, 1.0f, 1.0f, trans); diff --git a/WDE.MapRenderer/Utils/Gizmo.cs b/WDE.MapRenderer/Utils/Gizmo.cs index e5c50b7cb..8b60624f2 100644 --- a/WDE.MapRenderer/Utils/Gizmo.cs +++ b/WDE.MapRenderer/Utils/Gizmo.cs @@ -79,13 +79,13 @@ public HitType HitTest(IGameContext context, out Vector3 intersectionPoint) public void Render(IGameContext context) { - InternalRender(context, 0.5f); - InternalRender(context, 1); + InternalRender(context, true); + InternalRender(context, false); } - private void InternalRender(IGameContext context, float alpha) + private void InternalRender(IGameContext context, bool transparent) { - if (alpha < 1) + if (transparent) { material.BlendingEnabled = true; material.SourceBlending = Blending.SrcAlpha; @@ -105,7 +105,7 @@ private void InternalRender(IGameContext context, float alpha) t.Scale = Vector3.One * (float)Math.Sqrt(Math.Clamp(dist, 0.5f, 500) / 15); // +X (wow) t.Rotation = ArrowX; - material.SetUniform("objectColor", new Vector4(0, 0, 1, alpha)); + material.SetUniform("objectColor", new Vector4(0, 0, 1, transparent ? 0.5f : 1f)); context.Engine.RenderManager.Render(arrowMesh, material, 0, t); t.Rotation = PlaneX; context.Engine.RenderManager.Render(dragPlaneMesh, material, 0, t); @@ -113,14 +113,14 @@ private void InternalRender(IGameContext context, float alpha) // +Y (wow) t.Rotation = ArrowY; - material.SetUniform("objectColor", new Vector4(0, 1, 0, alpha)); + material.SetUniform("objectColor", new Vector4(0, 1, 0, transparent ? 0.5f : 1f)); context.Engine.RenderManager.Render(arrowMesh, material, 0, t); t.Rotation = PlaneY; context.Engine.RenderManager.Render(dragPlaneMesh, material, 0, t); // +Z (wow) t.Rotation = ArrowZ; - material.SetUniform("objectColor", new Vector4(1, 0, 0, alpha)); + material.SetUniform("objectColor", new Vector4(1, 0, 0, transparent ? 0.5f : 1f)); context.Engine.RenderManager.Render(arrowMesh, material, 0, t); t.Rotation = PlaneZ; context.Engine.RenderManager.Render(dragPlaneMesh, material, 0, t); diff --git a/WDE.MpqReader/Structures/Light.cs b/WDE.MpqReader/Structures/Light.cs index ed960d384..7f7658e2c 100644 --- a/WDE.MpqReader/Structures/Light.cs +++ b/WDE.MpqReader/Structures/Light.cs @@ -126,6 +126,8 @@ public T GetAtTime(Time time) { if (Count == 1) return Values[0]; + if (Count == 0) + return default; int higherThan = -1; int lowerThan = -1; diff --git a/WDE.WorldMap/WoWMapRenderer.cs b/WDE.WorldMap/WoWMapRenderer.cs index 756060704..ccfa68f29 100644 --- a/WDE.WorldMap/WoWMapRenderer.cs +++ b/WDE.WorldMap/WoWMapRenderer.cs @@ -162,8 +162,10 @@ private void LoadIfNeeded() ++i; } - TopLeftVirtual = new Point(minX, minY); - BottomRightVirtual = new Point(maxX, maxY); + (minX, minY) = CoordsUtils.WorldToEditor(CoordsUtils.MinCoord, CoordsUtils.MinCoord); + (maxX, maxY) = CoordsUtils.WorldToEditor(CoordsUtils.MaxCoord, CoordsUtils.MaxCoord); + TopLeftVirtual = new Point(Math.Min(minX, maxX), Math.Min(minY, maxY)); + BottomRightVirtual = new Point(Math.Max(minX, maxX), Math.Max(minY, maxY)); } } } \ No newline at end of file diff --git a/WoWDatabaseEditor.Common/WDE.Common/Utils/ListExtensions.cs b/WoWDatabaseEditor.Common/WDE.Common/Utils/ListExtensions.cs index 805d011de..cfc5e78f5 100644 --- a/WoWDatabaseEditor.Common/WDE.Common/Utils/ListExtensions.cs +++ b/WoWDatabaseEditor.Common/WDE.Common/Utils/ListExtensions.cs @@ -63,5 +63,16 @@ public static int IndexIf(this IList list, Func pred) return default; } + + public static int IndexOf(this IReadOnlyList list, T item) + { + for (int i = 0; i < list.Count; ++i) + { + if (EqualityComparer.Default.Equals(list[i], item)) + return i; + } + + return -1; + } } } \ No newline at end of file