├── Docs
├── Easy.png
├── Hard.png
├── Medium.png
├── Problem.png
├── TrayIcon.png
├── DesertWorld.png
├── FrozenWorld.png
├── GreenWorld.png
├── LevelEditor.png
├── CustomLevel1.png
├── CustomLevel2.png
├── CopyDialogLunarLander.gif
└── NuclearWastelandWorld.png
├── Resources
└── LunarLander.ico
├── .gitmodules
├── cgmanifest.json
├── Source
├── Program.cs
├── App.config
├── Utils
│ ├── WindowCapture.cs
│ └── NativeInterop.cs
├── GameOverlay
│ ├── GameInterface.cs
│ └── OverlayWindow.cs
├── app.manifest
├── LunarLander
│ ├── LunarLanderOverlayWindow.cs
│ ├── LunarDraw.cs
│ └── LunarSim.cs
└── CopyWatcher
│ ├── CopyWatcherApplication.cs
│ └── CopyWatcher.cs
├── .gitignore
├── LICENSE
├── Properties
├── AssemblyInfo.cs
├── Resources.Designer.cs
└── Resources.resx
├── packages.config
├── Copy Dialog Lunar Lander.sln
├── .gitattributes
├── README.md
└── Copy Dialog Lunar Lander.csproj
/Docs/Easy.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sanakan8472/copy-dialog-lunar-lander/HEAD/Docs/Easy.png
--------------------------------------------------------------------------------
/Docs/Hard.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sanakan8472/copy-dialog-lunar-lander/HEAD/Docs/Hard.png
--------------------------------------------------------------------------------
/Docs/Medium.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sanakan8472/copy-dialog-lunar-lander/HEAD/Docs/Medium.png
--------------------------------------------------------------------------------
/Docs/Problem.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sanakan8472/copy-dialog-lunar-lander/HEAD/Docs/Problem.png
--------------------------------------------------------------------------------
/Docs/TrayIcon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sanakan8472/copy-dialog-lunar-lander/HEAD/Docs/TrayIcon.png
--------------------------------------------------------------------------------
/Docs/DesertWorld.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sanakan8472/copy-dialog-lunar-lander/HEAD/Docs/DesertWorld.png
--------------------------------------------------------------------------------
/Docs/FrozenWorld.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sanakan8472/copy-dialog-lunar-lander/HEAD/Docs/FrozenWorld.png
--------------------------------------------------------------------------------
/Docs/GreenWorld.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sanakan8472/copy-dialog-lunar-lander/HEAD/Docs/GreenWorld.png
--------------------------------------------------------------------------------
/Docs/LevelEditor.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sanakan8472/copy-dialog-lunar-lander/HEAD/Docs/LevelEditor.png
--------------------------------------------------------------------------------
/Docs/CustomLevel1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sanakan8472/copy-dialog-lunar-lander/HEAD/Docs/CustomLevel1.png
--------------------------------------------------------------------------------
/Docs/CustomLevel2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sanakan8472/copy-dialog-lunar-lander/HEAD/Docs/CustomLevel2.png
--------------------------------------------------------------------------------
/Resources/LunarLander.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sanakan8472/copy-dialog-lunar-lander/HEAD/Resources/LunarLander.ico
--------------------------------------------------------------------------------
/Docs/CopyDialogLunarLander.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sanakan8472/copy-dialog-lunar-lander/HEAD/Docs/CopyDialogLunarLander.gif
--------------------------------------------------------------------------------
/Docs/NuclearWastelandWorld.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sanakan8472/copy-dialog-lunar-lander/HEAD/Docs/NuclearWastelandWorld.png
--------------------------------------------------------------------------------
/.gitmodules:
--------------------------------------------------------------------------------
1 | [submodule "box2d-netstandard"]
2 | path = box2d-netstandard
3 | url = https://github.com/Sanakan8472/box2d-netstandard
4 |
--------------------------------------------------------------------------------
/cgmanifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "Registrations": [
3 | {
4 | "component": {
5 | "type": "git",
6 | "git": {
7 | "repositoryUrl": "https://github.com/codingben/box2d-netstandard",
8 | "commitHash": "113db001379a1d485125fd932a9918304cfadb75"
9 | }
10 | }
11 | }
12 | ]
13 | }
--------------------------------------------------------------------------------
/Source/Program.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Windows;
3 |
4 | namespace CopyDialogLunarLander
5 | {
6 | class Program
7 | {
8 | [STAThread]
9 | static void Main(string[] args)
10 | {
11 | CopyWatcherApplication app = new CopyWatcherApplication();
12 | app.Run();
13 | }
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Prerequisites
2 | *.d
3 |
4 | # Compiled Object files
5 | *.slo
6 | *.lo
7 | *.o
8 | *.obj
9 |
10 | # Precompiled Headers
11 | *.gch
12 | *.pch
13 |
14 | # Compiled Dynamic libraries
15 | *.so
16 | *.dylib
17 | *.dll
18 |
19 | # Fortran module files
20 | *.mod
21 | *.smod
22 |
23 | # Compiled Static libraries
24 | *.lai
25 | *.la
26 | *.a
27 | *.lib
28 |
29 | # Executables
30 | *.exe
31 | *.out
32 | *.app
33 | bin/
34 | packages/
35 | obj/
36 | .vs/
37 |
--------------------------------------------------------------------------------
/Source/App.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2022 Sanakan8472
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 | using System.Runtime.CompilerServices;
3 | using System.Runtime.InteropServices;
4 |
5 | // General Information about an assembly is controlled through the following
6 | // set of attributes. Change these attribute values to modify the information
7 | // associated with an assembly.
8 | [assembly: AssemblyTitle("CopyDialogLunarLander")]
9 | [assembly: AssemblyDescription("")]
10 | [assembly: AssemblyConfiguration("")]
11 | [assembly: AssemblyCompany("")]
12 | [assembly: AssemblyProduct("CopyDialogLunarLander")]
13 | [assembly: AssemblyCopyright("Copyright © 2022")]
14 | [assembly: AssemblyTrademark("")]
15 | [assembly: AssemblyCulture("")]
16 |
17 | // Setting ComVisible to false makes the types in this assembly not visible
18 | // to COM components. If you need to access a type in this assembly from
19 | // COM, set the ComVisible attribute to true on that type.
20 | [assembly: ComVisible(false)]
21 |
22 | // The following GUID is for the ID of the typelib if this project is exposed to COM
23 | [assembly: Guid("c244f3b7-8b79-48fd-ad76-ca5583811d8d")]
24 |
25 | // Version information for an assembly consists of the following four values:
26 | //
27 | // Major Version
28 | // Minor Version
29 | // Build Number
30 | // Revision
31 | //
32 | // You can specify all the values or you can default the Build and Revision Numbers
33 | // by using the '*' as shown below:
34 | // [assembly: AssemblyVersion("1.0.*")]
35 | [assembly: AssemblyVersion("1.2.0.0")]
36 | [assembly: AssemblyFileVersion("1.2.0.0")]
37 |
--------------------------------------------------------------------------------
/Source/Utils/WindowCapture.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Drawing;
3 |
4 | namespace CopyDialogLunarLander
5 | {
6 | class WindowCapture
7 | {
8 | ///
9 | /// Captures the given native window as a bitmap.
10 | ///
11 | ///
12 | ///
13 | public static Bitmap Capture(IntPtr sourceWindow)
14 | {
15 | Bitmap bitmap = null;
16 | try
17 | {
18 | NativeInterop.RECT area;
19 | if (!NativeInterop.GetWindowRect(sourceWindow, out area))
20 | {
21 | return bitmap;
22 | }
23 |
24 | IntPtr screenDC = NativeInterop.GetDCEx((sourceWindow), IntPtr.Zero, NativeInterop.DeviceContextValues.Validate);
25 | IntPtr memDC = NativeInterop.CreateCompatibleDC(screenDC);
26 | IntPtr hBitmap = NativeInterop.CreateCompatibleBitmap(screenDC, (int)area.Width, (int)area.Height);
27 | NativeInterop.SelectObject(memDC, hBitmap); // Select bitmap from compatible bitmap to memDC
28 |
29 | NativeInterop.BitBlt(memDC, 0, 0, (int)area.Width, (int)area.Height, screenDC, 0, 0, NativeInterop.TernaryRasterOperations.SRCCOPY);
30 | bitmap = Image.FromHbitmap(hBitmap);
31 |
32 | NativeInterop.DeleteObject(hBitmap);
33 | NativeInterop.DeleteDC(memDC);
34 | NativeInterop.ReleaseDC(IntPtr.Zero, screenDC);
35 | }
36 | catch (Exception)
37 | {
38 |
39 | }
40 | return bitmap;
41 | }
42 |
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/packages.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/Copy Dialog Lunar Lander.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 17
4 | VisualStudioVersion = 17.7.34018.315
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Copy Dialog Lunar Lander", "Copy Dialog Lunar Lander.csproj", "{C244F3B7-8B79-48FD-AD76-CA5583811D8D}"
7 | EndProject
8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Box2D.NetStandard", "box2d-netstandard\src\box2dx\Box2D.NetStandard\Box2D.NetStandard.csproj", "{8A1FCE19-725D-4DF7-83A4-079752BE2E9C}"
9 | EndProject
10 | Project("{54435603-DBB4-11D2-8724-00A0C9A8B90C}") = "Installer", "Installer\Installer.vdproj", "{AB55BF39-46F3-4576-B9E9-76EF39256BF3}"
11 | EndProject
12 | Global
13 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
14 | Debug|Any CPU = Debug|Any CPU
15 | Release|Any CPU = Release|Any CPU
16 | EndGlobalSection
17 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
18 | {C244F3B7-8B79-48FD-AD76-CA5583811D8D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
19 | {C244F3B7-8B79-48FD-AD76-CA5583811D8D}.Debug|Any CPU.Build.0 = Debug|Any CPU
20 | {C244F3B7-8B79-48FD-AD76-CA5583811D8D}.Release|Any CPU.ActiveCfg = Release|Any CPU
21 | {C244F3B7-8B79-48FD-AD76-CA5583811D8D}.Release|Any CPU.Build.0 = Release|Any CPU
22 | {8A1FCE19-725D-4DF7-83A4-079752BE2E9C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
23 | {8A1FCE19-725D-4DF7-83A4-079752BE2E9C}.Debug|Any CPU.Build.0 = Debug|Any CPU
24 | {8A1FCE19-725D-4DF7-83A4-079752BE2E9C}.Release|Any CPU.ActiveCfg = Release|Any CPU
25 | {8A1FCE19-725D-4DF7-83A4-079752BE2E9C}.Release|Any CPU.Build.0 = Release|Any CPU
26 | {AB55BF39-46F3-4576-B9E9-76EF39256BF3}.Debug|Any CPU.ActiveCfg = Debug
27 | {AB55BF39-46F3-4576-B9E9-76EF39256BF3}.Release|Any CPU.ActiveCfg = Release
28 | EndGlobalSection
29 | GlobalSection(SolutionProperties) = preSolution
30 | HideSolutionNode = FALSE
31 | EndGlobalSection
32 | GlobalSection(ExtensibilityGlobals) = postSolution
33 | SolutionGuid = {CB63D6F4-F9E2-477D-93FD-4AD2EE3A0E26}
34 | EndGlobalSection
35 | EndGlobal
36 |
--------------------------------------------------------------------------------
/Source/GameOverlay/GameInterface.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Drawing;
4 | using System.Drawing.Imaging;
5 | using System.Linq;
6 | using System.Text;
7 | using System.Threading.Tasks;
8 | using System.Windows;
9 | using System.Windows.Automation;
10 | using System.Windows.Controls;
11 | using System.Windows.Media;
12 | using System.Windows.Threading;
13 | using System.Windows.Interop;
14 | using System.Runtime.InteropServices;
15 | using System.Windows.Input;
16 | using System.Windows.Media.Imaging;
17 | using System.Diagnostics;
18 | using System.ComponentModel;
19 |
20 |
21 | namespace CopyDialogLunarLander
22 | {
23 | ///
24 | /// Any new game needs to derive from OverlayWindow and implement this GameInterface.
25 | ///
26 | interface GameInterface
27 | {
28 | ///
29 | /// Called once to define the virtual game space. This is usually 395x85.
30 | ///
31 | /// Logical size of the world. This is independent of DPI scaling and does not neccessarily match pixels.
32 | void Init(System.Windows.Size worldSize);
33 |
34 | ///
35 | /// Called before the window is destroyed. Use this for any game related cleanup.
36 | ///
37 | void DeInit();
38 |
39 | ///
40 | /// Each frame a height field is extracted from the progress chart.
41 | ///
42 | /// Element count is worldSize.width and values are between 0 (bottom) and 1 (top). Due to DPI scaling the values can be between units of the virtual game space.
43 | /// The color of the copy progress bar
44 | void HeightFieldUpdated(float[] heightField, System.Drawing.Color terrainColor);
45 |
46 | ///
47 | /// Called at 60hz. This is the main game loop function.
48 | ///
49 | /// Render any content to this image
50 | /// Use this to determine time stepping of the simulation and profiling
51 | void Update(DrawingGroup backingStore, OverlayStats stats);
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | ###############################################################################
2 | # Set default behavior to automatically normalize line endings.
3 | ###############################################################################
4 | * text=auto
5 |
6 | ###############################################################################
7 | # Set default behavior for command prompt diff.
8 | #
9 | # This is need for earlier builds of msysgit that does not have it on by
10 | # default for csharp files.
11 | # Note: This is only used by command line
12 | ###############################################################################
13 | #*.cs diff=csharp
14 |
15 | ###############################################################################
16 | # Set the merge driver for project and solution files
17 | #
18 | # Merging from the command prompt will add diff markers to the files if there
19 | # are conflicts (Merging from VS is not affected by the settings below, in VS
20 | # the diff markers are never inserted). Diff markers may cause the following
21 | # file extensions to fail to load in VS. An alternative would be to treat
22 | # these files as binary and thus will always conflict and require user
23 | # intervention with every merge. To do so, just uncomment the entries below
24 | ###############################################################################
25 | #*.sln merge=binary
26 | #*.csproj merge=binary
27 | #*.vbproj merge=binary
28 | #*.vcxproj merge=binary
29 | #*.vcproj merge=binary
30 | #*.dbproj merge=binary
31 | #*.fsproj merge=binary
32 | #*.lsproj merge=binary
33 | #*.wixproj merge=binary
34 | #*.modelproj merge=binary
35 | #*.sqlproj merge=binary
36 | #*.wwaproj merge=binary
37 |
38 | ###############################################################################
39 | # behavior for image files
40 | #
41 | # image files are treated as binary by default.
42 | ###############################################################################
43 | #*.jpg binary
44 | #*.png binary
45 | #*.gif binary
46 |
47 | ###############################################################################
48 | # diff behavior for common document formats
49 | #
50 | # Convert binary document formats to text before diffing them. This feature
51 | # is only available from the command line. Turn it on by uncommenting the
52 | # entries below.
53 | ###############################################################################
54 | #*.doc diff=astextplain
55 | #*.DOC diff=astextplain
56 | #*.docx diff=astextplain
57 | #*.DOCX diff=astextplain
58 | #*.dot diff=astextplain
59 | #*.DOT diff=astextplain
60 | #*.pdf diff=astextplain
61 | #*.PDF diff=astextplain
62 | #*.rtf diff=astextplain
63 | #*.RTF diff=astextplain
64 |
--------------------------------------------------------------------------------
/Properties/Resources.Designer.cs:
--------------------------------------------------------------------------------
1 | //------------------------------------------------------------------------------
2 | //
3 | // This code was generated by a tool.
4 | // Runtime Version:4.0.30319.42000
5 | //
6 | // Changes to this file may cause incorrect behavior and will be lost if
7 | // the code is regenerated.
8 | //
9 | //------------------------------------------------------------------------------
10 |
11 | namespace CopyDialogLunarLander.Properties {
12 | using System;
13 |
14 |
15 | ///
16 | /// A strongly-typed resource class, for looking up localized strings, etc.
17 | ///
18 | // This class was auto-generated by the StronglyTypedResourceBuilder
19 | // class via a tool like ResGen or Visual Studio.
20 | // To add or remove a member, edit your .ResX file then rerun ResGen
21 | // with the /str option, or rebuild your VS project.
22 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")]
23 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
24 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
25 | internal class Resources {
26 |
27 | private static global::System.Resources.ResourceManager resourceMan;
28 |
29 | private static global::System.Globalization.CultureInfo resourceCulture;
30 |
31 | [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
32 | internal Resources() {
33 | }
34 |
35 | ///
36 | /// Returns the cached ResourceManager instance used by this class.
37 | ///
38 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
39 | internal static global::System.Resources.ResourceManager ResourceManager {
40 | get {
41 | if (object.ReferenceEquals(resourceMan, null)) {
42 | global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("CopyDialogLunarLander.Properties.Resources", typeof(Resources).Assembly);
43 | resourceMan = temp;
44 | }
45 | return resourceMan;
46 | }
47 | }
48 |
49 | ///
50 | /// Overrides the current thread's CurrentUICulture property for all
51 | /// resource lookups using this strongly typed resource class.
52 | ///
53 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
54 | internal static global::System.Globalization.CultureInfo Culture {
55 | get {
56 | return resourceCulture;
57 | }
58 | set {
59 | resourceCulture = value;
60 | }
61 | }
62 |
63 | ///
64 | /// Looks up a localized resource of type System.Drawing.Icon similar to (Icon).
65 | ///
66 | internal static System.Drawing.Icon LunarLander {
67 | get {
68 | object obj = ResourceManager.GetObject("LunarLander", resourceCulture);
69 | return ((System.Drawing.Icon)(obj));
70 | }
71 | }
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/Source/app.manifest:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
54 |
55 |
56 |
57 | true
58 | PerMonitor
59 |
60 |
61 |
62 |
63 |
64 |
78 |
79 |
80 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Copy Dialog Lunar Lander
2 |
3 | Here is one of the most pressing issues of our times: You need to download a huge file from the other side of the world to continue your work. You literally can't do anything but wait.
4 |
5 | 
6 |
7 | Now there is finally a solution to this problem! Play lunar lander in these inviting hills generated by the progress dialog. Time flies when you are having fun!
8 |
9 | 
10 |
11 | ## Multiple difficulty settings!
12 | There are multiple difficulty settings to choose from:
13 | * **Easy**: Just copy a huge file from a local SSD to another local SSD.\
14 | 
15 | * **Medium**: Copy a big file to a crappy USB 2.0 stick.\
16 | 
17 | * **Hard**: Probably your work VPN that connects you to the office on the other side of the world. As a plus point, you have more time to complete the level here. Way more time.\
18 | 
19 |
20 | ## Infinite levels, infinite replayability!
21 | There are infinite levels, thanks to general unreliability of storage devices, the internet and VPNs.
22 | Even playing the same 'level' twice might result in totally different landscapes, infinite replayability is guaranteed!
23 |
24 | ## Visit many exciting worlds!
25 | Unlike the lunar lander from the olden times, this one is not limited to visiting just the moon, here are some other worlds you can visit:
26 | * **Lush green forest**: The default world you visit. Just copy any file to reach this world.\
27 | 
28 | * **Scorching hot desert**: Just press *pause* on your copy operation to visit this world.\
29 | 
30 | * **Frozen ice planet**: You can visit this world by enabling *high contrast mode* in Windows.\
31 | 
32 | * **Nuclear wasteland**: By combining *pause* and *high contrast mode* from above you can unlock this nightmarish world.\
33 | 
34 |
35 | ## Create your own worlds (with the OS built-in level editor)!
36 | If the above worlds are not your cup of tea, you can create your own! Simply enable *high contrast mode* and change the *Hyperlinks* color for the *paused* world and the *Selected Text* color for the normal world. That's 16.7 million worlds that you can visit!
37 |
38 | \
39 | Here some examples of what worlds await you:
40 |
41 | 
42 | 
43 |
44 | # Building
45 |
46 | Needs at least Windows 10 for handling dpi scaling correctly. Only tested on Windows 10 21H2 so far. Windows 11 is untested.
47 |
48 | * `git clone` into some folder and go there.
49 | * `git submodule init`
50 | * `git submodule update`
51 | * Open `Copy Dialog Lunar Lander.sln` in Visual Studio 2019.
52 | * Build and run.
53 |
54 | # Gameplay
55 |
56 | Once running, a tray icon is added that can be used to exit the app, change the difficulty in case your moon lander explodes even on flat terrain.
57 | * **Hard** means you need to be below 5m/s when touching the ground.
58 | * **Easy** eases the limit to below 10m/s.
59 |
60 | 
61 |
62 | While running, the app will detect all windows copy dialogs and paint a game overlay on top of them while they are focused. You can then click in the progress graph to begin playing.
63 | Controls:
64 | * **left, right, down** arrow keys control the thrusters.
65 | * **space** restarts the game.
66 | * **tab** enables debug rendering and frame statistics which can be used to see how badly optimized my code is.
67 |
68 | # Contributing
69 |
70 | Contributions are always welcome!\
71 | You can even make new games by deriving from `OverlayWindow` and implementing `GameInterface`. Any non-abstract class will show up in the tray icon so you can switch to your new game.\
72 | Your imagination is only limited by the 395x85 logical units size of the progress chart.
73 |
74 | If you have too much time on your hands, consider contributing to [box2d-standard](https://github.com/codingben/box2d-netstandard) instead. It's used in this project to drive the lunar lander (mostly into the ground and then to explode).
75 |
76 | # Known Issues
77 |
78 | * Sometimes clicking on the **Click here to play** message does not work and the game overlay gets unfocused. Not sure why that is. Just click twice again until it works.
79 | * Dragging from one monitor to another with a different DPI causes the overlay to spasm out. Just drag the copy dialog a bit further and it will fix itself. Not sure why that happens but I already wasted too much time on this joke so I left it as is but hey, [contributions](#contributing) are always welcome!
80 |
--------------------------------------------------------------------------------
/Source/Utils/NativeInterop.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Runtime.InteropServices;
3 |
4 | namespace CopyDialogLunarLander
5 | {
6 | class NativeInterop
7 | {
8 | #region user32 Imports
9 |
10 | [DllImport("user32.dll")]
11 | public static extern uint GetDpiForWindow(IntPtr hwnd);
12 |
13 | public const Int32 MONITOR_DEFAULTTONEAREST = 0x00000002;
14 |
15 | [Flags()]
16 | public enum DeviceContextValues : uint
17 | {
18 | Window = 0x00000001,
19 | Cache = 0x00000002,
20 | NoResetAttrs = 0x00000004,
21 | ClipChildren = 0x00000008,
22 | ClipSiblings = 0x00000010,
23 | ParentClip = 0x00000020,
24 | ExcludeRgn = 0x00000040,
25 | IntersectRgn = 0x00000080,
26 | ExcludeUpdate = 0x00000100,
27 | IntersectUpdate = 0x00000200,
28 | LockWindowUpdate = 0x00000400,
29 | UseStyle = 0x00010000,
30 | Validate = 0x00200000,
31 | }
32 |
33 | [DllImport("user32.dll")]
34 | public static extern IntPtr GetDCEx(IntPtr hWnd, IntPtr hrgnClip, DeviceContextValues flags);
35 |
36 | [DllImport("user32.dll")]
37 | public static extern IntPtr GetDC(IntPtr hWnd);
38 |
39 | [StructLayout(LayoutKind.Sequential)]
40 | public struct POINT
41 | {
42 | public int x;
43 | public int y;
44 | }
45 |
46 | [StructLayout(LayoutKind.Sequential)]
47 | public struct RECT
48 | {
49 | public int Left; // x position of upper-left corner
50 | public int Top; // y position of upper-left corner
51 | public int Right; // x position of lower-right corner
52 | public int Bottom; // y position of lower-right corner
53 |
54 | public double Width
55 | {
56 | get { return Right - Left; }
57 | }
58 |
59 | public double Height
60 | {
61 | get { return Bottom - Top; }
62 | }
63 | }
64 |
65 | [DllImport("user32.dll")]
66 | public static extern IntPtr GetParent(IntPtr hWnd);
67 |
68 | [DllImport("user32.dll")]
69 | [return: MarshalAs(UnmanagedType.Bool)]
70 | public static extern bool GetWindowRect(IntPtr hWnd, out RECT rect);
71 |
72 | [DllImport("user32.dll")]
73 | public static extern int ReleaseDC(IntPtr hWnd, IntPtr hDC);
74 |
75 | [DllImport("user32.dll")]
76 | public static extern bool IsWindowVisible(IntPtr hWnd);
77 |
78 | [DllImport("user32.dll")]
79 | public static extern IntPtr GetForegroundWindow();
80 |
81 | [DllImport("user32.dll")]
82 | public static extern IntPtr GetWindowThreadProcessId(IntPtr hWnd, out uint ProcessId);
83 |
84 | #endregion user32 Imports
85 |
86 | #region gdi32 Imports
87 |
88 | [DllImport("gdi32.dll", ExactSpelling = true, PreserveSig = true, SetLastError = true)]
89 | public static extern IntPtr SelectObject(IntPtr hdc, IntPtr hgdiobj);
90 |
91 | [DllImport("gdi32.dll")]
92 | public static extern IntPtr CreateCompatibleBitmap(IntPtr hdc, int nWidth, int nHeight);
93 |
94 | [DllImport("gdi32.dll", SetLastError = true)]
95 | public static extern IntPtr CreateCompatibleDC(IntPtr hdc);
96 |
97 | [DllImport("gdi32.dll")]
98 | public static extern bool DeleteObject(IntPtr hObject);
99 |
100 | [DllImport("gdi32.dll")]
101 | public static extern bool DeleteDC(IntPtr hObject);
102 |
103 | [DllImport("gdi32.dll")]
104 | public static extern IntPtr CreateBitmap(int nWidth, int nHeight, uint cPlanes, uint cBitsPerPel, IntPtr lpvBits);
105 |
106 | public enum TernaryRasterOperations : uint
107 | {
108 | SRCCOPY = 0x00CC0020,
109 | SRCPAINT = 0x00EE0086,
110 | SRCAND = 0x008800C6,
111 | SRCINVERT = 0x00660046,
112 | SRCERASE = 0x00440328,
113 | NOTSRCCOPY = 0x00330008,
114 | NOTSRCERASE = 0x001100A6,
115 | MERGECOPY = 0x00C000CA,
116 | MERGEPAINT = 0x00BB0226,
117 | PATCOPY = 0x00F00021,
118 | PATPAINT = 0x00FB0A09,
119 | PATINVERT = 0x005A0049,
120 | DSTINVERT = 0x00550009,
121 | BLACKNESS = 0x00000042,
122 | WHITENESS = 0x00FF0062,
123 | }
124 |
125 | [DllImport("gdi32.dll")]
126 | public static extern bool BitBlt(IntPtr hdc, int nXDest, int nYDest, int nWidth, int nHeight, IntPtr hdcSrc, int nXSrc, int nYSrc, TernaryRasterOperations dwRop);
127 |
128 | #endregion gdi32 Imports
129 | }
130 | }
131 |
--------------------------------------------------------------------------------
/Source/LunarLander/LunarLanderOverlayWindow.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Drawing;
4 | using System.Drawing.Imaging;
5 | using System.Linq;
6 | using System.Text;
7 | using System.Threading.Tasks;
8 | using System.Windows;
9 | using System.Windows.Input;
10 | using System.Windows.Media;
11 |
12 | namespace CopyDialogLunarLander
13 | {
14 | public class LunarLanderOverlayWindow : OverlayWindow
15 | {
16 | private LunarSim _sim = new LunarSim();
17 | private bool _left = false;
18 | private bool _right = false;
19 | private bool _down = false;
20 | private bool _debug = false;
21 |
22 | private static System.Windows.Forms.ToolStripMenuItem _menuEasy;
23 | private static System.Windows.Forms.ToolStripMenuItem _menuHard;
24 |
25 | public LunarLanderOverlayWindow()
26 | {
27 | }
28 |
29 | #region FillOptions
30 | static void FillOptions(System.Windows.Forms.ToolStripMenuItem optionsMenu)
31 | {
32 | if (_menuEasy == null)
33 | {
34 | _menuEasy = (System.Windows.Forms.ToolStripMenuItem)optionsMenu.DropDownItems.Add("Easy");
35 | _menuEasy.Click += EasySelected;
36 | }
37 | else
38 | {
39 | optionsMenu.DropDownItems.Add(_menuEasy);
40 | }
41 | if (_menuHard == null)
42 | {
43 | _menuHard = (System.Windows.Forms.ToolStripMenuItem)optionsMenu.DropDownItems.Add("Hard");
44 | _menuHard.Click += HardSelected;
45 | _menuHard.Checked = true;
46 | }
47 | else
48 | {
49 | optionsMenu.DropDownItems.Add(_menuHard);
50 | }
51 | }
52 |
53 |
54 | private static void EasySelected(object sender, EventArgs e)
55 | {
56 | LunarSim.k_maxSpeed = 10;
57 | _menuEasy.Checked = true;
58 | _menuHard.Checked = false;
59 | }
60 | private static void HardSelected(object sender, EventArgs e)
61 | {
62 | LunarSim.k_maxSpeed = 5;
63 | _menuEasy.Checked = false;
64 | _menuHard.Checked = true;
65 | }
66 |
67 | #endregion FillOptions
68 |
69 | #region GameInterface
70 |
71 | public override void Init(System.Windows.Size worldSize)
72 | {
73 | _sim.Init(worldSize);
74 | }
75 |
76 | public override void DeInit()
77 | {
78 | _sim.Dispose();
79 | }
80 |
81 | public override void HeightFieldUpdated(float[] heightField, System.Drawing.Color terrainColor)
82 | {
83 | _sim.SetHeightField(heightField, terrainColor);
84 | }
85 |
86 | public override void Update(DrawingGroup _backingStore, OverlayStats stats)
87 | {
88 | _sim.SetActive(this.IsActive);
89 | _sim.Input(_left, _right, _down);
90 |
91 | var drawingContext = _backingStore.Open();
92 |
93 | // The drawingContext coordinate system has 0,0 in the upper left corner.
94 | // However, for box2d it's the lower left corner so we flip the coordinate space here.
95 | drawingContext.PushTransform(new ScaleTransform(1 / 1, -1 /1));
96 | drawingContext.PushTransform(new TranslateTransform(0, -Height * 1));
97 | // This will execute the debug render hook of box2d which we use to render our scene.
98 | _sim.Step(1.0f / (float)stats.fps, drawingContext);
99 | drawingContext.Pop();
100 | drawingContext.Pop();
101 | // We don't want text to be upside-down so the normal render method is outside the transform block above.
102 | _sim.Render(drawingContext);
103 |
104 | if (_debug)
105 | {
106 | LunarSceneDraw.DrawText(drawingContext, stats.GetStatsString(), new System.Windows.Point(4, Height - 12), 12D, System.Drawing.Color.Black);
107 | }
108 | drawingContext.Close();
109 | }
110 |
111 | #endregion GameInterface
112 |
113 | #region Input
114 |
115 | protected override void OnKeyDown(KeyEventArgs e)
116 | {
117 | if (e.Key == Key.Down)
118 | _down = true;
119 | if (e.Key == Key.Left)
120 | _left = true;
121 | if (e.Key == Key.Right)
122 | _right = true;
123 | if (e.Key == Key.Space)
124 | _sim.Reset();
125 | base.OnKeyDown(e);
126 | }
127 |
128 | protected override void OnKeyUp(KeyEventArgs e)
129 | {
130 | if (e.Key == Key.Down)
131 | _down = false;
132 | if (e.Key == Key.Left)
133 | _left = false;
134 | if (e.Key == Key.Right)
135 | _right = false;
136 | if (e.Key == Key.Tab)
137 | {
138 | _debug = !_debug;
139 | _sim.SetDebug(_debug);
140 | }
141 | base.OnKeyUp(e);
142 | }
143 |
144 | protected override void OnMouseLeftButtonUp(MouseButtonEventArgs e)
145 | {
146 | // Debug code, adds a box under the cursor
147 | /*
148 | PresentationSource MainWindowPresentationSource = PresentationSource.FromVisual(this);
149 | Matrix m = MainWindowPresentationSource.CompositionTarget.TransformToDevice;
150 |
151 | var scenePos = e.GetPosition(this);
152 | scenePos.Y = Height - scenePos.Y;
153 | _sim.AddBox(scenePos);
154 | */
155 | }
156 |
157 | #endregion Input
158 |
159 | }
160 | }
161 |
--------------------------------------------------------------------------------
/Properties/Resources.resx:
--------------------------------------------------------------------------------
1 |
2 |
3 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 | text/microsoft-resx
110 |
111 |
112 | 2.0
113 |
114 |
115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
116 |
117 |
118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
119 |
120 |
121 |
122 | ..\Resources\LunarLander.ico;System.Drawing.Icon, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
123 |
124 |
--------------------------------------------------------------------------------
/Source/LunarLander/LunarDraw.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 |
7 | using Box2DX.Common;
8 | using Box2DX.Collision;
9 | using Box2DX.Dynamics;
10 | using System.Windows.Media;
11 | using System.Windows;
12 |
13 | namespace CopyDialogLunarLander
14 | {
15 | ///
16 | /// Use when rendering the scene. Only renders solid polys.
17 | ///
18 | public class LunarSceneDraw : LunarDraw
19 | {
20 | public Box2DX.Dynamics.Color OverrideColor;
21 |
22 | public override void DrawPolygon(Vec2[] vertices, int vertexCount, Box2DX.Dynamics.Color color)
23 | {
24 | }
25 |
26 | public override void DrawSolidPolygon(Vec2[] vertices, int vertexCount, Box2DX.Dynamics.Color color)
27 | {
28 | base.DrawSolidPolygon(vertices, vertexCount, OverrideColor);
29 | }
30 |
31 | public override void DrawCircle(Vec2 center, float radius, Box2DX.Dynamics.Color color)
32 | {
33 | }
34 |
35 | public override void DrawSolidCircle(Vec2 center, float radius, Vec2 axis, Box2DX.Dynamics.Color color)
36 | {
37 | DrawingContext.DrawEllipse(ToBrush(OverrideColor), ToPen(OverrideColor), ToPoint(center), radius, radius);
38 | }
39 |
40 | public override void DrawSegment(Vec2 p1, Vec2 p2, Box2DX.Dynamics.Color color)
41 | {
42 | }
43 |
44 | public override void DrawXForm(XForm xf)
45 | {
46 | }
47 | }
48 |
49 | ///
50 | /// For debug visualization. Draws everything as red wireframe.
51 | ///
52 | public class LunarDebugSceneDraw : LunarDraw
53 | {
54 | private Box2DX.Dynamics.Color _debugColor = new Box2DX.Dynamics.Color(1, 0, 0);
55 |
56 | public override void DrawSolidPolygon(Box2DX.Common.Vec2[] vertices, int vertexCount, Box2DX.Dynamics.Color color)
57 | {
58 | DrawPolygon(vertices, vertexCount, _debugColor);
59 | }
60 |
61 | public override void DrawCircle(Box2DX.Common.Vec2 center, float radius, Box2DX.Dynamics.Color color)
62 | {
63 | DrawingContext.DrawEllipse(null, ToPen(_debugColor), ToPoint(center), radius, radius);
64 | }
65 |
66 | public override void DrawSolidCircle(Box2DX.Common.Vec2 center, float radius, Box2DX.Common.Vec2 axis, Box2DX.Dynamics.Color color)
67 | {
68 | DrawCircle(center, radius, _debugColor);
69 | }
70 |
71 | public override void DrawSegment(Box2DX.Common.Vec2 p1, Box2DX.Common.Vec2 p2, Box2DX.Dynamics.Color color)
72 | {
73 | DrawingContext.DrawLine(ToPen(_debugColor), ToPoint(p1), ToPoint(p2));
74 | }
75 | }
76 |
77 | ///
78 | /// Draws on the DrawingContext of the file copy dialog.
79 | ///
80 | public class LunarDraw : DebugDraw
81 | {
82 | public System.Windows.Media.DrawingContext DrawingContext { get; set; }
83 |
84 | public override void DrawPolygon(Box2DX.Common.Vec2[] vertices, int vertexCount, Box2DX.Dynamics.Color color)
85 | {
86 | for (int i = 0; i < vertexCount; i++)
87 | {
88 | DrawSegment(vertices[i], vertices[(i + 1) % vertexCount], color);
89 | }
90 | }
91 |
92 | public override void DrawSolidPolygon(Box2DX.Common.Vec2[] vertices, int vertexCount, Box2DX.Dynamics.Color color)
93 | {
94 | StreamGeometry geo = new StreamGeometry();
95 | geo.FillRule = FillRule.EvenOdd;
96 |
97 | // Open the context to use for drawing.
98 | using (StreamGeometryContext context = geo.Open())
99 | {
100 | var vertices2 = vertices.Take(vertexCount);
101 | context.BeginFigure(ToPoint(vertices2.First()), true, true);
102 | context.PolyLineTo(vertices2.Skip(1).Select(x => ToPoint(x)).ToArray(), true, false);
103 | }
104 | DrawingContext.DrawGeometry(ToBrush(color), ToPen(color), geo);
105 | }
106 |
107 | public override void DrawCircle(Box2DX.Common.Vec2 center, float radius, Box2DX.Dynamics.Color color)
108 | {
109 | DrawingContext.DrawEllipse(null, ToPen(color), ToPoint(center), radius, radius);
110 | }
111 |
112 | public override void DrawSolidCircle(Box2DX.Common.Vec2 center, float radius, Box2DX.Common.Vec2 axis, Box2DX.Dynamics.Color color)
113 | {
114 | DrawCircle(center, radius, color);
115 | }
116 |
117 | public override void DrawSegment(Box2DX.Common.Vec2 p1, Box2DX.Common.Vec2 p2, Box2DX.Dynamics.Color color)
118 | {
119 | DrawingContext.DrawLine(ToPen(color), ToPoint(p1), ToPoint(p2));
120 | }
121 |
122 | public override void DrawXForm(Box2DX.Common.XForm xf)
123 | {
124 | Vec2 p1 = xf.Position, p2;
125 | float k_axisScale = 0.4f;
126 | p2 = p1 + k_axisScale * xf.R.Col1;
127 | DrawSegment(p1, p2, new Box2DX.Dynamics.Color(1.0f, 0.0f, 0.0f));
128 | p2 = p1 + k_axisScale * xf.R.Col2;
129 | DrawSegment(p1, p2, new Box2DX.Dynamics.Color(0.0f, 1.0f, 0.0f));
130 | }
131 |
132 | public static void DrawText(DrawingContext drawingContext, string text, System.Windows.Point pos, double size, System.Drawing.Color color)
133 | {
134 | FormattedText ft = new FormattedText(text,
135 | new System.Globalization.CultureInfo("en-us"),
136 | FlowDirection.LeftToRight,
137 | new Typeface(new System.Windows.Media.FontFamily("Consolas"), FontStyles.Normal, FontWeights.Normal, new FontStretch()),
138 | size,
139 | new SolidColorBrush(new System.Windows.Media.Color() { R = color.R, G = color.G, B = color.B, A = 255 }));
140 | drawingContext.DrawText(ft, pos);
141 | }
142 | public System.Windows.Point ToPoint(Box2DX.Common.Vec2 point)
143 | {
144 | return new System.Windows.Point(point.X, point.Y);
145 | }
146 |
147 | public System.Windows.Media.Pen ToPen(Box2DX.Dynamics.Color color)
148 | {
149 | System.Windows.Media.Pen pen = new System.Windows.Media.Pen();
150 | pen.Brush = new SolidColorBrush(new System.Windows.Media.Color() { R = (byte)(color.R * 255.0f), G = (byte)(color.G * 255.0f), B = (byte)(color.B * 255.0f), A = 255 });
151 | return pen;
152 | }
153 |
154 | public System.Windows.Media.Brush ToBrush(Box2DX.Dynamics.Color color)
155 | {
156 | var Brush = new SolidColorBrush(new System.Windows.Media.Color() { R = (byte)(color.R * 255.0f), G = (byte)(color.G * 255.0f), B = (byte)(color.B * 255.0f), A = 255 });
157 | return Brush;
158 | }
159 | }
160 | }
161 |
--------------------------------------------------------------------------------
/Source/CopyWatcher/CopyWatcherApplication.cs:
--------------------------------------------------------------------------------
1 | using System.Windows;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Linq;
5 | using System.Text;
6 | using System.Threading.Tasks;
7 | using System.Threading;
8 | using System.Windows.Automation;
9 | using System.ComponentModel;
10 | using System.Reflection;
11 | using Microsoft.Win32;
12 |
13 | namespace CopyDialogLunarLander
14 | {
15 | ///
16 | /// Dummy window that is never shown but needs to exist to prevent the app from exiting.
17 | ///
18 | class MainWindow : Window
19 | {
20 | }
21 |
22 | ///
23 | /// CopyWatcherApplication application creates a new OverlayWindow for every copy dialog and manages the tray icon.
24 | ///
25 | class CopyWatcherApplication : Application
26 | {
27 | struct OverlayWindowData
28 | {
29 | public OverlayWindow window;
30 | public AutomationElement chartView;
31 | public IntPtr parentOperationStatusWindow;
32 | }
33 |
34 | struct Game
35 | {
36 | public Type type;
37 | public System.Windows.Forms.ToolStripMenuItem menuItem;
38 | }
39 |
40 | private System.Windows.Forms.NotifyIcon _notifyIcon;
41 |
42 | private OperationStatusWindowWatcher _watcher = new OperationStatusWindowWatcher();
43 | private Dictionary _overlayWindows = new Dictionary();
44 | private List _games = new List();
45 | private Type _currentGame = null;
46 | private System.Windows.Forms.ToolStripMenuItem _gameOptions = null;
47 | private System.Windows.Forms.ToolStripMenuItem _autostart = null;
48 |
49 | const string _autostartRegKey = "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run";
50 | const string _autostartName = "CopyDialogLunarLander";
51 |
52 | public CopyWatcherApplication()
53 | {
54 | }
55 |
56 | protected override void OnStartup(StartupEventArgs e)
57 | {
58 | base.OnStartup(e);
59 | // Dummy window that is never shown just so the app doesn't exit if no file ops are active.
60 | MainWindow = new MainWindow();
61 |
62 | _notifyIcon = new System.Windows.Forms.NotifyIcon();
63 | _notifyIcon.Icon = CopyDialogLunarLander.Properties.Resources.LunarLander;
64 | _notifyIcon.Visible = true;
65 | CreateContextMenu();
66 |
67 | _watcher.ChartViewOpened += OnChartViewOpened;
68 | _watcher.ChartViewClosed += OnChartViewClosed;
69 | _watcher.Start();
70 | }
71 |
72 | private void CreateContextMenu()
73 | {
74 | _notifyIcon.ContextMenuStrip = new System.Windows.Forms.ContextMenuStrip();
75 | {
76 | var gamesMenu = (System.Windows.Forms.ToolStripMenuItem)_notifyIcon.ContextMenuStrip.Items.Add("Games");
77 | var type = typeof(OverlayWindow);
78 | var types = AppDomain.CurrentDomain.GetAssemblies().SelectMany(s => s.GetTypes()).Where(p => type.IsAssignableFrom(p) && p != type && !p.IsAbstract).ToList();
79 | _currentGame = typeof(LunarLanderOverlayWindow);
80 | foreach (var derivedType in types)
81 | {
82 | var game = (System.Windows.Forms.ToolStripMenuItem)gamesMenu.DropDownItems.Add("Lunar Lander");
83 | game.Checked = derivedType == _currentGame;
84 | game.Click += GameChanged;
85 | _games.Add(new Game { type = derivedType, menuItem = game });
86 | }
87 | }
88 | _gameOptions = (System.Windows.Forms.ToolStripMenuItem)_notifyIcon.ContextMenuStrip.Items.Add("Game Options");
89 | _autostart = (System.Windows.Forms.ToolStripMenuItem)_notifyIcon.ContextMenuStrip.Items.Add("Start with Windows");
90 | _autostart.Click += (s, e) => ToggleAutostart();
91 | _autostart.Checked = IsAutostartEnabled();
92 | _notifyIcon.ContextMenuStrip.Items.Add("-");
93 | _notifyIcon.ContextMenuStrip.Items.Add("Exit Lunar Lander").Click += (s, e) => ExitApplication();
94 |
95 | UpdateGameOptions();
96 | }
97 |
98 | private void GameChanged(object sender, EventArgs e)
99 | {
100 | try
101 | {
102 | var game = _games.FirstOrDefault(x => x.menuItem == sender);
103 | if (game.type != null)// && !game.menuItem.Checked)
104 | {
105 | _currentGame = game.type;
106 | _games.ForEach(x => x.menuItem.Checked = x.menuItem == game.menuItem);
107 |
108 | var values = _overlayWindows.Values.ToList();
109 | foreach (var item in values)
110 | {
111 | DestroyOverlay(item.chartView);
112 | }
113 | foreach (var item in values)
114 | {
115 | CreateOverlay(item.chartView, item.parentOperationStatusWindow);
116 | }
117 | UpdateGameOptions();
118 | }
119 | }
120 | catch (Exception ex)
121 | {
122 | System.Diagnostics.Debug.WriteLine($"Exception: {ex}");
123 | }
124 | }
125 |
126 | private void UpdateGameOptions()
127 | {
128 | _gameOptions.DropDownItems.Clear();
129 |
130 | MethodInfo info = _currentGame.GetMethod("FillOptions", BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static | BindingFlags.FlattenHierarchy);
131 |
132 | object value = info.Invoke(null, new object[] { _gameOptions });
133 | }
134 |
135 | private bool IsAutostartEnabled()
136 | {
137 | try
138 | {
139 | RegistryKey registryKey = Registry.CurrentUser.OpenSubKey(_autostartRegKey, false);
140 | var value = registryKey.GetValue(_autostartName);
141 | string exePath = Assembly.GetEntryAssembly().Location;
142 | if (value is string && (string)value == exePath)
143 | {
144 | return true;
145 | }
146 | }
147 | catch (Exception ex)
148 | {
149 | System.Diagnostics.Debug.WriteLine($"Exception: {ex}");
150 | }
151 | return false;
152 | }
153 |
154 | private void ToggleAutostart()
155 | {
156 | try
157 | {
158 | bool enabled = IsAutostartEnabled();
159 | RegistryKey registryKey = Registry.CurrentUser.OpenSubKey(_autostartRegKey, true);
160 | if (!enabled)
161 | {
162 | string exePath = Assembly.GetEntryAssembly().Location;
163 | registryKey.SetValue(_autostartName, exePath);
164 | _autostart.Checked = true;
165 | }
166 | else
167 | {
168 | registryKey.DeleteValue(_autostartName);
169 | _autostart.Checked = false;
170 | }
171 | }
172 | catch (Exception ex)
173 | {
174 | System.Diagnostics.Debug.WriteLine($"Exception: {ex}");
175 | }
176 | }
177 |
178 | private void ExitApplication()
179 | {
180 | _watcher.Dispose();
181 | var values = _overlayWindows.Values.ToList();
182 | foreach (var item in values)
183 | {
184 | DestroyOverlay(item.chartView);
185 | }
186 | _overlayWindows.Clear();
187 |
188 | _notifyIcon.Dispose();
189 | _notifyIcon = null;
190 | MainWindow.Close();
191 | }
192 |
193 | private void CreateOverlay(AutomationElement chartView, IntPtr parentOperationStatusWindow)
194 | {
195 | System.Diagnostics.Debug.WriteLine($"Progress chart opened {chartView.Current.NativeWindowHandle}");
196 | OverlayWindow ow = (OverlayWindow)Activator.CreateInstance(_currentGame);
197 | if (!_overlayWindows.ContainsKey(chartView))
198 | {
199 | _overlayWindows.Add(chartView, new OverlayWindowData { window = ow, chartView = chartView, parentOperationStatusWindow = parentOperationStatusWindow });
200 | ow.Wake(chartView, parentOperationStatusWindow);
201 | }
202 | }
203 |
204 | private void DestroyOverlay(AutomationElement chartView)
205 | {
206 | System.Diagnostics.Debug.WriteLine($"Progress chart closed");
207 | if (_overlayWindows.ContainsKey(chartView))
208 | {
209 | _overlayWindows[chartView].window.Close();
210 | _overlayWindows.Remove(chartView);
211 | }
212 | }
213 |
214 | public void OnChartViewOpened(AutomationElement chartView, IntPtr parentOperationStatusWindow)
215 | {
216 | Dispatcher.BeginInvoke(new Action(() =>
217 | {
218 | CreateOverlay(chartView, parentOperationStatusWindow);
219 | }));
220 | }
221 |
222 | public void OnChartViewClosed(AutomationElement chartView, IntPtr parentOperationStatusWindow)
223 | {
224 | Dispatcher.BeginInvoke(new Action(() =>
225 | {
226 | DestroyOverlay(chartView);
227 | }));
228 | }
229 | }
230 | }
231 |
--------------------------------------------------------------------------------
/Copy Dialog Lunar Lander.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Debug
6 | AnyCPU
7 | {C244F3B7-8B79-48FD-AD76-CA5583811D8D}
8 | WinExe
9 | CopyDialogLunarLander
10 | CopyDialogLunarLander
11 | v4.7.1
12 | 512
13 | true
14 |
15 |
16 |
17 | x86
18 | true
19 | full
20 | false
21 | bin\Debug\
22 | DEBUG;TRACE
23 | prompt
24 | 4
25 | true
26 |
27 |
28 | AnyCPU
29 | pdbonly
30 | true
31 | bin\Release\
32 | TRACE
33 | prompt
34 | 4
35 | true
36 |
37 |
38 |
39 |
40 |
41 | Source\app.manifest
42 |
43 |
44 |
45 | False
46 | True
47 | packages\UIAComWrapper.1.1.0.14\lib\net40\Interop.UIAutomationClient.dll
48 |
49 |
50 | packages\Microsoft.Win32.Primitives.4.3.0\lib\net46\Microsoft.Win32.Primitives.dll
51 |
52 |
53 |
54 |
55 |
56 | packages\System.AppContext.4.3.0\lib\net46\System.AppContext.dll
57 |
58 |
59 | packages\System.Console.4.3.0\lib\net46\System.Console.dll
60 |
61 |
62 |
63 |
64 | packages\System.Globalization.Calendars.4.3.0\lib\net46\System.Globalization.Calendars.dll
65 |
66 |
67 | packages\System.IO.Compression.4.3.0\lib\net46\System.IO.Compression.dll
68 |
69 |
70 |
71 | packages\System.IO.Compression.ZipFile.4.3.0\lib\net46\System.IO.Compression.ZipFile.dll
72 |
73 |
74 | packages\System.IO.FileSystem.4.3.0\lib\net46\System.IO.FileSystem.dll
75 |
76 |
77 | packages\System.IO.FileSystem.Primitives.4.3.0\lib\net46\System.IO.FileSystem.Primitives.dll
78 |
79 |
80 | packages\System.Net.Http.4.3.2\lib\net46\System.Net.Http.dll
81 |
82 |
83 | packages\System.Net.Sockets.4.3.0\lib\net46\System.Net.Sockets.dll
84 |
85 |
86 | packages\System.Runtime.InteropServices.RuntimeInformation.4.3.0\lib\net45\System.Runtime.InteropServices.RuntimeInformation.dll
87 |
88 |
89 | packages\System.Security.Cryptography.Algorithms.4.3.0\lib\net46\System.Security.Cryptography.Algorithms.dll
90 |
91 |
92 | packages\System.Security.Cryptography.Encoding.4.3.0\lib\net46\System.Security.Cryptography.Encoding.dll
93 |
94 |
95 | packages\System.Security.Cryptography.Primitives.4.3.0\lib\net46\System.Security.Cryptography.Primitives.dll
96 |
97 |
98 | packages\System.Security.Cryptography.X509Certificates.4.3.0\lib\net46\System.Security.Cryptography.X509Certificates.dll
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 | packages\System.Xml.ReaderWriter.4.3.0\lib\net46\System.Xml.ReaderWriter.dll
109 |
110 |
111 | False
112 | packages\UIAComWrapper.1.1.0.14\lib\net40\UIAComWrapper.dll
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 | True
130 | True
131 | Resources.resx
132 |
133 |
134 |
135 |
136 | Designer
137 |
138 |
139 |
140 |
141 |
142 |
143 | ResXFileCodeGenerator
144 | Resources.Designer.cs
145 | Designer
146 |
147 |
148 |
149 |
150 |
151 |
152 |
153 | {8a1fce19-725d-4df7-83a4-079752be2e9c}
154 | Box2D.NetStandard
155 |
156 |
157 |
158 |
159 |
160 | Interop.UIAutomationClient.dll
161 | PreserveNewest
162 |
163 |
164 |
165 |
--------------------------------------------------------------------------------
/Source/CopyWatcher/CopyWatcher.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading;
6 | using System.Threading.Tasks;
7 | using System.Windows.Automation;
8 |
9 | namespace CopyDialogLunarLander
10 | {
11 | ///
12 | /// Detects new copy dialogs being opened and closed as well as new charts added to existing dialogs.
13 | ///
14 | class OperationStatusWindowWatcher : IDisposable
15 | {
16 | public delegate void ChartViewOpenedHandler(AutomationElement chartView, IntPtr parentOperationStatusWindow);
17 | public event ChartViewOpenedHandler ChartViewOpened;
18 | public delegate void ChartViewClosedHandler(AutomationElement chartView, IntPtr parentOperationStatusWindow);
19 | public event ChartViewClosedHandler ChartViewClosed;
20 |
21 | private PropertyCondition _chartViewCondition = new PropertyCondition(AutomationElement.ClassNameProperty, "ChartView", PropertyConditionFlags.IgnoreCase);
22 | private PropertyCondition _operationStatusWindowCondition = new PropertyCondition(AutomationElement.ClassNameProperty, "OperationStatusWindow");
23 | private AutomationEventHandler _eventHandler;
24 | private Task _backgroundUpdater;
25 | private bool _runBackgroundTask = true;
26 |
27 | class StatusWindow
28 | {
29 | public AutomationElement statusWindow;
30 | public List progressWindows = new List();
31 | }
32 |
33 | struct ProgressChart
34 | {
35 | public AutomationElement progressChart;
36 | public IntPtr nativeHWND;
37 | }
38 | private Dictionary _trackedWindows = new Dictionary();
39 |
40 | public OperationStatusWindowWatcher()
41 | {
42 | }
43 |
44 | public void Start()
45 | {
46 | Task.Factory.StartNew(() => FindWindows());
47 |
48 | {
49 | // Configure automation event handler.
50 | _eventHandler = new AutomationEventHandler(OnWindowOpen);
51 | Automation.AddAutomationEventHandler(WindowPattern.WindowOpenedEvent, AutomationElement.RootElement, TreeScope.Children, _eventHandler);
52 | Automation.AddAutomationEventHandler(WindowPattern.WindowClosedEvent, AutomationElement.RootElement, TreeScope.Children, _eventHandler);
53 | }
54 |
55 | // Start background updater.
56 | _backgroundUpdater = Task.Run(async () =>
57 | {
58 | try
59 | {
60 | while (_runBackgroundTask)
61 | {
62 | await Task.Delay(2000);
63 |
64 | if (_runBackgroundTask)
65 | UpdatStatusWindows();
66 | }
67 | }
68 | catch (Exception ex)
69 | {
70 | System.Diagnostics.Debug.WriteLine($"Exception: {ex}");
71 | }
72 |
73 | });
74 | }
75 |
76 | private void FindWindows()
77 | {
78 | AutomationElement rootElement = AutomationElement.RootElement;
79 | AutomationElementCollection elementCollection = rootElement.FindAll(TreeScope.Children, _operationStatusWindowCondition);
80 | foreach (AutomationElement elem in elementCollection)
81 | {
82 | AddStatusWindow(elem);
83 | }
84 | }
85 |
86 | private void AddStatusWindow(AutomationElement statusWindow)
87 | {
88 | lock (_trackedWindows)
89 | {
90 | try
91 | {
92 | var sw = new StatusWindow();
93 | sw.statusWindow = statusWindow;
94 | IntPtr nativeRoot = new IntPtr((int)statusWindow.Current.NativeWindowHandle);
95 | System.Diagnostics.Debug.WriteLine($"AddStatusWindow: {nativeRoot}");
96 | AutomationElementCollection progressCharts = statusWindow.FindAll(TreeScope.Subtree, _chartViewCondition);
97 | foreach (AutomationElement pc in progressCharts)
98 | {
99 | sw.progressWindows.Add(new ProgressChart { progressChart = pc, nativeHWND = new IntPtr((int)pc.Current.NativeWindowHandle) });
100 | ChartViewOpened?.Invoke(pc, nativeRoot);
101 | }
102 |
103 | StatusWindow swOld;
104 | if (_trackedWindows.TryGetValue(nativeRoot, out swOld))
105 | {
106 | // This can happen as the explorer does not destroy the window but simply hides it when the last operations finishes.
107 | swOld.statusWindow = sw.statusWindow;
108 | swOld.progressWindows.AddRange(sw.progressWindows);
109 | }
110 | else
111 | {
112 | _trackedWindows.Add(nativeRoot, sw);
113 | }
114 | }
115 | catch (Exception ex)
116 | {
117 | System.Diagnostics.Debug.WriteLine($"Exception: {ex}");
118 | }
119 | }
120 | }
121 |
122 | private void RemoveStatusWindow(AutomationElement statusWindow)
123 | {
124 | lock (_trackedWindows)
125 | {
126 | try
127 | {
128 | IntPtr nativeRoot = new IntPtr((int)statusWindow.Current.NativeWindowHandle);
129 | System.Diagnostics.Debug.WriteLine($"RemoveStatusWindow: {nativeRoot}");
130 |
131 | StatusWindow sw;
132 | if (_trackedWindows.TryGetValue(nativeRoot, out sw))
133 | {
134 | foreach (var pw in sw.progressWindows)
135 | {
136 | ChartViewClosed?.Invoke(pw.progressChart, nativeRoot);
137 | }
138 | _trackedWindows.Remove(nativeRoot);
139 | }
140 | }
141 | catch (Exception ex)
142 | {
143 | System.Diagnostics.Debug.WriteLine($"Exception: {ex}");
144 | }
145 | }
146 | }
147 |
148 | private void UpdatStatusWindows()
149 | {
150 | lock (_trackedWindows)
151 | {
152 |
153 | foreach (var item in _trackedWindows)
154 | {
155 | try
156 | {
157 | List oldCharts = item.Value.progressWindows.Select(x => x.nativeHWND).ToList();
158 | List newCharts = new List();
159 | AutomationElementCollection progressCharts = item.Value.statusWindow.FindAll(TreeScope.Subtree, _chartViewCondition);
160 | foreach (AutomationElement pc in progressCharts)
161 | {
162 | IntPtr nativeRoot = new IntPtr((int)pc.Current.NativeWindowHandle);
163 | newCharts.Add(nativeRoot);
164 | }
165 |
166 | // Remove old
167 | var toRemove = item.Value.progressWindows.Where(x => !newCharts.Contains(x.nativeHWND)).ToList();
168 | foreach (var remove in toRemove)
169 | {
170 | ChartViewClosed?.Invoke(remove.progressChart, item.Key);
171 | }
172 | item.Value.progressWindows.RemoveAll(x => !newCharts.Contains(x.nativeHWND));
173 |
174 | // Add new
175 | foreach (AutomationElement pc in progressCharts)
176 | {
177 | IntPtr nativeRoot = new IntPtr((int)pc.Current.NativeWindowHandle);
178 | if (!oldCharts.Contains(nativeRoot))
179 | {
180 | item.Value.progressWindows.Add(new ProgressChart { progressChart = pc, nativeHWND = new IntPtr((int)pc.Current.NativeWindowHandle) });
181 | ChartViewOpened?.Invoke(pc, item.Key);
182 | }
183 | }
184 | }
185 | catch (Exception ex)
186 | {
187 | System.Diagnostics.Debug.WriteLine($"Exception: {ex}");
188 | }
189 | }
190 |
191 | }
192 | }
193 |
194 | private void OnWindowOpen(object src, AutomationEventArgs e)
195 | {
196 | // Make sure the element still exists. Elements such as tooltips
197 | // can disappear before the event is processed.
198 | AutomationElement sourceElement;
199 | try
200 | {
201 | sourceElement = src as AutomationElement;
202 | if (sourceElement.Current.ClassName != "OperationStatusWindow")
203 | return;
204 | }
205 | catch (Exception ex)
206 | {
207 | System.Diagnostics.Debug.WriteLine($"Exception: {ex}");
208 | return;
209 | }
210 |
211 | if (e.EventId == WindowPattern.WindowOpenedEvent)
212 | AddStatusWindow(sourceElement);
213 | else if (e.EventId == WindowPattern.WindowClosedEvent)
214 | RemoveStatusWindow(sourceElement);
215 | }
216 |
217 | #region IDisposable Support
218 | private bool disposedValue = false; // To detect redundant calls
219 |
220 | protected virtual void Dispose(bool disposing)
221 | {
222 | if (!disposedValue)
223 | {
224 | if (disposing)
225 | {
226 | _runBackgroundTask = false;
227 | if (_backgroundUpdater != null)
228 | {
229 | _backgroundUpdater.Wait();
230 | _backgroundUpdater = null;
231 | }
232 | }
233 |
234 | Automation.RemoveAutomationEventHandler(WindowPattern.WindowOpenedEvent, AutomationElement.RootElement, _eventHandler);
235 | Automation.RemoveAutomationEventHandler(WindowPattern.WindowClosedEvent, AutomationElement.RootElement, _eventHandler);
236 |
237 | disposedValue = true;
238 | }
239 | }
240 |
241 | // todo: override a finalizer only if dispose(bool disposing) above has code to free unmanaged resources.
242 | ~OperationStatusWindowWatcher()
243 | {
244 | // do not change this code. put cleanup code in dispose(bool disposing) above.
245 | Dispose(false);
246 | }
247 |
248 | // This code added to correctly implement the disposable pattern.
249 | public void Dispose()
250 | {
251 | // Do not change this code. Put cleanup code in Dispose(bool disposing) above.
252 | Dispose(true);
253 | GC.SuppressFinalize(this);
254 | }
255 | #endregion
256 | }
257 | }
258 |
--------------------------------------------------------------------------------
/Source/GameOverlay/OverlayWindow.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Drawing;
4 | using System.Drawing.Imaging;
5 | using System.Linq;
6 | using System.Text;
7 | using System.Threading.Tasks;
8 | using System.Windows;
9 | using System.Windows.Automation;
10 | using System.Windows.Controls;
11 | using System.Windows.Media;
12 | using System.Windows.Threading;
13 | using System.Windows.Interop;
14 | using System.Runtime.InteropServices;
15 | using System.Windows.Input;
16 | using System.Windows.Media.Imaging;
17 | using System.Diagnostics;
18 | using System.ComponentModel;
19 |
20 | namespace CopyDialogLunarLander
21 | {
22 | ///
23 | /// Current frame stats.
24 | ///
25 | public struct OverlayStats
26 | {
27 | public double captureTime;
28 | public double updateTime;
29 | public double frameTimer;
30 | public double fps;
31 |
32 | public string GetStatsString()
33 | {
34 | return $"Capture: {captureTime.ToString("0.00")} Update: {updateTime.ToString("0.00")} FPS: {fps.ToString("00.00")} Frame: {frameTimer.ToString("00.00")}";
35 | }
36 | }
37 |
38 |
39 | ///
40 | /// Base class for windows attached to a single progress chart in a operation status dialog window.
41 | /// Any new game needs to derive from this class and implement GameInterface.
42 | /// A game can also implement "static void FillOptions(System.Windows.Forms.ToolStripMenuItem optionsMenu)" to add options to the tray icon.
43 | ///
44 | public abstract class OverlayWindow : Window, GameInterface
45 | {
46 | enum State
47 | {
48 | Sleeping,
49 | LookingForChart,
50 | Playing
51 | }
52 |
53 | private AutomationElement _trackedChartView = null;
54 | private IntPtr _parentOperationStatusWindow = IntPtr.Zero;
55 |
56 | private static OverlayWindow _activeOverlayWindow = null;
57 |
58 | private PropertyCondition _progressChartCondition = new PropertyCondition(AutomationElement.ClassNameProperty, "RateChartOverlayWindow", PropertyConditionFlags.None);
59 | private AutomationElement _progressChart = null;
60 |
61 | private State _state = State.Sleeping;
62 | private System.Windows.Rect _rect = new Rect();
63 |
64 | System.Drawing.Color _terrainColor = System.Drawing.Color.Empty;
65 | DrawingGroup _backingStore = new DrawingGroup();
66 |
67 | private OverlayStats _stats = new OverlayStats();
68 | private Stopwatch _fpsStopWatch = null;
69 | private long _frames = 0;
70 |
71 | public OverlayWindow()
72 | {
73 | AllowsTransparency = true;
74 | WindowStyle = WindowStyle.None;
75 | // Alpha needs to be at least 1 or the window input will click through to the underlying window.
76 | Background = new SolidColorBrush(new System.Windows.Media.Color() { R = 0, G = 0, B = 0, A = 1 });
77 | Topmost = true;
78 | ShowInTaskbar = false;
79 | //RenderOptions.SetEdgeMode(this, EdgeMode.Aliased);
80 |
81 | Width = 200;
82 | Height = 100;
83 | Left = 100;
84 | Top = 100;
85 |
86 | _stats.fps = 30.0f;
87 | }
88 |
89 | #region GameInterface
90 |
91 | public abstract void Init(System.Windows.Size worldSize);
92 | public abstract void DeInit();
93 | public abstract void HeightFieldUpdated(float[] heightField, System.Drawing.Color terrainColor);
94 | public abstract void Update(DrawingGroup backingStore, OverlayStats stats);
95 |
96 | #endregion GameInterface
97 |
98 | #region Private functions
99 |
100 | private void CompositionTarget_Rendering(object sender, EventArgs e)
101 | {
102 | Stopwatch sw = Stopwatch.StartNew();
103 | try
104 | {
105 | TrackChartView();
106 | }
107 | catch (System.Windows.Automation.ElementNotAvailableException)
108 | {
109 | Sleep();
110 | }
111 | _stats.frameTimer = sw.ElapsedMilliseconds;
112 | }
113 |
114 | public void Wake(AutomationElement trackedCharView, IntPtr parentOperationStatusWindow)
115 | {
116 | if (_state != State.Sleeping)
117 | return;
118 |
119 | _fpsStopWatch = Stopwatch.StartNew();
120 | _state = State.LookingForChart;
121 | this._trackedChartView = trackedCharView;
122 | this._parentOperationStatusWindow = parentOperationStatusWindow;
123 |
124 | var hwnd = new IntPtr(trackedCharView.Current.NativeWindowHandle);
125 | uint dpi = NativeInterop.GetDpiForWindow(hwnd);
126 | // BoundingRectangle is in physical device coordinates (pixels), we need to convert to logical units first.
127 | _rect = _trackedChartView.Current.BoundingRectangle;
128 | _rect = new Rect(0, 0, Math.Ceiling(_rect.Width / dpi * 96), Math.Ceiling(_rect.Height / dpi * 96));
129 | // This should be equivalent to these device independent constants but not sure if that is true for all OS versions.
130 | // _rect = new Rect(0, 0, 395.0, 85.0);
131 | Init(new System.Windows.Size(_rect.Width, _rect.Height));
132 |
133 | try
134 | {
135 | TrackChartView();
136 | CompositionTarget.Rendering += CompositionTarget_Rendering;
137 | }
138 | catch (System.Windows.Automation.ElementNotAvailableException)
139 | {
140 | Sleep();
141 | }
142 | }
143 |
144 |
145 | public void Sleep()
146 | {
147 | CompositionTarget.Rendering -= CompositionTarget_Rendering;
148 | _state = State.Sleeping;
149 | Hide();
150 | }
151 |
152 | protected override void OnClosing(CancelEventArgs e)
153 | {
154 | if (_activeOverlayWindow == this)
155 | {
156 | _activeOverlayWindow = null;
157 | }
158 | DeInit();
159 | Sleep();
160 | base.OnClosing(e);
161 | }
162 |
163 | public void TrackChartView()
164 | {
165 | if (_trackedChartView == null)
166 | {
167 | Sleep();
168 | return;
169 | }
170 |
171 | if (_progressChart == null)
172 | {
173 | _progressChart = _trackedChartView.FindFirst(TreeScope.Descendants, _progressChartCondition);
174 | }
175 | // The progress chart can be minimized by clicking 'fewer details'. The progress chart itself shouldn't be used for
176 | // anything else as any queries on it glitches out windows automation and pure win32 API calls.
177 | if (_progressChart != null && NativeInterop.IsWindowVisible(new IntPtr(_progressChart.Current.NativeWindowHandle)))
178 | {
179 | _state = State.Playing;
180 |
181 | Show();
182 |
183 | bool showOverlay = false;
184 | try
185 | {
186 | IntPtr hwndFocus = NativeInterop.GetForegroundWindow();
187 | IntPtr hwndCurrent = new IntPtr((int)_trackedChartView.Current.NativeWindowHandle);
188 | var wih = new System.Windows.Interop.WindowInteropHelper(this);
189 | IntPtr hwndThis = wih.Handle;
190 | // IsActive function is unreliable for always in foreground windows so we compute it manually.
191 | bool isActive = hwndThis == hwndFocus;
192 |
193 | if (isActive && _activeOverlayWindow != this)
194 | {
195 | _activeOverlayWindow = this;
196 | System.Diagnostics.Debug.WriteLine($"Active: {hwndCurrent}, parent OW: {_parentOperationStatusWindow}");
197 | }
198 | else if (!isActive && _activeOverlayWindow == this)
199 | {
200 | _activeOverlayWindow = null;
201 | System.Diagnostics.Debug.WriteLine($"Not Active: {hwndCurrent}, parent OW: {_parentOperationStatusWindow}");
202 | }
203 | if (_activeOverlayWindow != null && _activeOverlayWindow._parentOperationStatusWindow == _parentOperationStatusWindow)
204 | {
205 | showOverlay = true;
206 | }
207 |
208 | while (hwndCurrent != IntPtr.Zero)
209 | {
210 | if (hwndCurrent == hwndFocus)
211 | {
212 | showOverlay = true;
213 | break;
214 | }
215 | hwndCurrent = NativeInterop.GetParent(hwndCurrent);
216 | }
217 |
218 | showOverlay = showOverlay || isActive;
219 | }
220 | catch (Exception)
221 | {
222 | }
223 |
224 | {
225 | // BoundingRectangle is in physical device coordinates (pixel) units. Convert to logical.
226 | var rect = _trackedChartView.Current.BoundingRectangle;
227 | var pos = this.PointFromScreen(new System.Windows.Point(rect.Left, rect.Top));
228 | var pos2 = this.PointFromScreen(new System.Windows.Point(rect.Right, rect.Bottom));
229 | // Convert to absolute coordinates by adding the current pos to them.
230 | pos.X += Left;
231 | pos.Y += Top;
232 | pos2.X += Left;
233 | pos2.Y += Top;
234 |
235 | Width = pos2.X - pos.X;
236 | Height = pos2.Y - pos.Y;
237 | Left = pos.X;
238 | // Hacky way of moving the overlay out of the way.
239 | Top = showOverlay ? pos.Y : 20000 ;
240 | }
241 |
242 | {
243 | Stopwatch sw = Stopwatch.StartNew();
244 | UpdateHeightfield();
245 | _stats.captureTime = sw.Elapsed.TotalMilliseconds;
246 |
247 | sw.Restart();
248 | Update(_backingStore, _stats);
249 | _stats.updateTime = sw.Elapsed.TotalMilliseconds;
250 |
251 | _frames++;
252 | if (_fpsStopWatch.Elapsed.TotalMilliseconds > 1000)
253 | {
254 | _stats.fps = (double)_frames / ((double)_fpsStopWatch.ElapsedMilliseconds / 1000.0);
255 | _stats.fps = Math.Max(_stats.fps, 30); // Do not fall below 30fps for sim stability, will cause slowdown instead
256 | _fpsStopWatch.Restart();
257 | _frames = 0;
258 | }
259 | }
260 | }
261 | else
262 | {
263 | _state = State.LookingForChart;
264 | Hide();
265 | }
266 | }
267 | protected override void OnRender(System.Windows.Media.DrawingContext drawingContext)
268 | {
269 | base.OnRender(drawingContext);
270 | drawingContext.DrawDrawing(_backingStore);
271 | }
272 |
273 | private void UpdateHeightfield()
274 | {
275 | if (_rect.Width == 0)
276 | return;
277 |
278 | _terrainColor = System.Drawing.Color.Empty;
279 | var value = _trackedChartView.GetCurrentPropertyValue(AutomationElement.NativeWindowHandleProperty);
280 | IntPtr nativeHandle = new IntPtr((int)value);
281 | Bitmap bmp = WindowCapture.Capture(nativeHandle);
282 | if (bmp != null)
283 | {
284 | BitmapData pixelData = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height), ImageLockMode.ReadOnly, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
285 |
286 | float[] heightField = new float[(int)_rect.Width];
287 | //try
288 | //{
289 | // bmp.Save("D:\\aaa.png");
290 | //}
291 | //catch (Exception)
292 | //{
293 | //}
294 |
295 | Dictionary accu = new Dictionary();
296 | accu[System.Drawing.Color.Empty] = 0;
297 | unsafe
298 | {
299 | // Find the terrain color by searching for all color above a certain threshold of
300 | // saturation and then picking the most common one.
301 | byte* pixelPtr = (byte*)pixelData.Scan0;
302 | for (int x = 2; x < 6; x++)
303 | {
304 | for (int y = 0; y < pixelData.Height; ++y)
305 | {
306 | byte* pixel = pixelPtr + (x * 4 + y * pixelData.Stride);
307 | byte b = pixel[0];
308 | byte g = pixel[1];
309 | byte r = pixel[2];
310 | if (r == g && r == b)
311 | continue;
312 |
313 | System.Drawing.Color color = System.Drawing.Color.FromArgb(r, g, b);
314 | float sat = color.GetSaturation();
315 | if (sat > 0.8)
316 | {
317 | if (!accu.ContainsKey(color))
318 | accu[color] = 0;
319 | accu[color]++;
320 | }
321 | }
322 | }
323 | _terrainColor = accu.Aggregate((l, r) => l.Value > r.Value ? l : r).Key;
324 |
325 | // Find where the terrain color stops, starting at the bottom of each column of pixels.
326 | if (_terrainColor != System.Drawing.Color.Empty)
327 | {
328 | Parallel.For(0, (int)_rect.Width, x =>
329 | {
330 | heightField[x] = 0;
331 | bool terrainStarted = false;
332 | int pixelPosX = (int)Math.Round(((double)x / _rect.Width) * pixelData.Width);
333 | for (int y = pixelData.Height - 1; y >= 0; --y)
334 | {
335 | byte* pixel = pixelPtr + (pixelPosX * 4 + y * pixelData.Stride);
336 | byte b = pixel[0];
337 | byte g = pixel[1];
338 | byte r = pixel[2];
339 |
340 | System.Drawing.Color color = System.Drawing.Color.FromArgb(r, g, b);
341 | if (color == System.Drawing.Color.FromArgb(0, 0, 0))
342 | continue;
343 | if (_terrainColor == color)
344 | {
345 | terrainStarted = true;
346 | }
347 | else if (_terrainColor != color && terrainStarted)
348 | {
349 | // Only pick the terrain height if we actually encountered a terrain pixel.
350 | // This is needed because on some systems there is a while line randomly at
351 | // the bottom which would otherwise stop traversal.
352 | heightField[x] = (float)(pixelData.Height - y - 1) / pixelData.Height;
353 | break;
354 | }
355 | else if (!terrainStarted && (pixelData.Height - y) > 5)
356 | {
357 | // If we didn't encounter a single terrain pixel in the bottom 5 pixels of a column,
358 | // there probably is no terrain here and we can stop searching.
359 | break;
360 | }
361 | }
362 | });
363 | }
364 | }
365 | bmp.UnlockBits(pixelData);
366 |
367 | if (_terrainColor != System.Drawing.Color.Empty)
368 | {
369 | HeightFieldUpdated(heightField, _terrainColor);
370 | }
371 |
372 | }
373 | }
374 | #endregion Private functions
375 |
376 | }
377 | }
378 |
--------------------------------------------------------------------------------
/Source/LunarLander/LunarSim.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 | using Box2DX.Common;
7 | using Box2DX.Collision;
8 | using Box2DX.Dynamics;
9 |
10 | namespace CopyDialogLunarLander
11 | {
12 | [FlagsAttribute]
13 | enum GameState
14 | {
15 | None = 0,
16 | Active = 1,
17 | Playing = 4,
18 | }
19 |
20 | enum ShapeType
21 | {
22 | Ground,
23 | Debris,
24 | LanderCockpit,
25 | LanderFoundation,
26 | LanderLeftLeg,
27 | LanderRightLeg
28 | }
29 |
30 | struct GameStats
31 | {
32 | public void Init()
33 | {
34 | Dead = false;
35 | Success = false;
36 | TargetLocation = new Vec2(0, 0);
37 | CurrentLocation = new Vec2(0, 0);
38 | Score = 0;
39 |
40 | Fuel = 10000;
41 | Speed = 0;
42 | LastStep = 0;
43 | WorldTime = 0.0f;
44 | LeftLegOnGroud = false;
45 | RightLegOnGroud = false;
46 | }
47 |
48 | public int GetScore()
49 | {
50 | return (int)System.Math.Round((Fuel / 10000.0f) * System.Math.Max(1.0f - System.Math.Abs(TargetLocation.X - CurrentLocation.X) / 100.0f, 0.0f) * 1000.0f);
51 | }
52 |
53 |
54 | public bool Dead;
55 | public bool Success;
56 | public Vec2 TargetLocation;
57 | public Vec2 CurrentLocation;
58 | public float Score;
59 |
60 | public float Fuel;
61 | public float Speed;
62 |
63 | public float LastStep;
64 | public float WorldTime;
65 |
66 | public bool LeftLegOnGroud;
67 | public bool RightLegOnGroud;
68 | }
69 |
70 | class LunarSim : IDisposable, ContactListener
71 | {
72 | class TerrainBlock
73 | {
74 | public TerrainBlock(int blockIndex, int blockSize)
75 | {
76 | _blockIndex = blockIndex;
77 | _blockSize = blockSize;
78 | }
79 |
80 | public void Clear(World world)
81 | {
82 | _poi = new Vec2(0,0);
83 | if (_bodies != null)
84 | {
85 | _bodies.ToList().ForEach(s => { world.DestroyBody(s); });
86 | _bodies = null;
87 | }
88 | }
89 |
90 | public void Update(World world, AABB _worldAABB, IList segment)
91 | {
92 | Clear(world);
93 |
94 | List vertices = new List(segment.Count);
95 | for (int i = 0; i < segment.Count; ++i)
96 | {
97 | vertices.Add(new Vec2(_blockIndex * _blockSize + i, _worldAABB.Extents.Y * 2 * segment[i]));
98 | }
99 |
100 | for (int i = vertices.Count - 2; i > 0; --i)
101 | {
102 | float middle = (vertices[i - 1].Y + vertices[i + 1].Y) / 2.0f;
103 | if (System.Math.Abs(middle - vertices[i].Y) < 0.05f)
104 | {
105 | vertices.RemoveAt(i);
106 | }
107 | }
108 |
109 | _bodies = new Body[vertices.Count - 1];
110 | _poi = vertices[0];
111 | for (int i = 0; i < vertices.Count - 1; ++i)
112 | {
113 | EdgeDef sd = new EdgeDef();
114 | sd.Vertex1 = vertices[i];
115 | sd.Vertex2 = vertices[i + 1];
116 | sd.Friction = 1;
117 | sd.UserData = ShapeType.Ground;
118 | BodyDef bd = new BodyDef();
119 | bd.Position.Set(0.0f, 0);
120 | bd.UserData = ShapeType.Ground;
121 |
122 | _bodies[i] = world.CreateBody(bd);
123 | _bodies[i].CreateFixture(sd);
124 | }
125 | }
126 |
127 | public Vec2 GetPointOfInterest()
128 | {
129 | return _poi;
130 | }
131 |
132 | public Body[] _bodies;
133 | int _blockIndex;
134 | int _blockSize;
135 | Vec2 _poi;
136 | }
137 |
138 | public class Debris
139 | {
140 | public Debris(World world, Body body, float lifetime)
141 | {
142 | _world = world;
143 | _body = body;
144 | _lifetime = lifetime;
145 | }
146 |
147 | public bool Step(float dt)
148 | {
149 | _lifetime -= dt;
150 | if (_lifetime < 0)
151 | {
152 | _world.DestroyBody(_body);
153 | _body = null;
154 | return true;
155 | }
156 | return false;
157 | }
158 |
159 | World _world;
160 | Body _body;
161 | float _lifetime;
162 | }
163 |
164 | public static float k_maxSpeed = 5;
165 |
166 | private Random _rnd = new Random();
167 |
168 | private AABB _worldAABB;
169 | private GameState _state = GameState.None;
170 | private GameStats _stats;
171 |
172 | private bool _debugMode = false;
173 | private LunarDraw _draw = new LunarDraw();
174 | private LunarDebugSceneDraw _debugSceneDraw = new LunarDebugSceneDraw();
175 | private LunarSceneDraw _lunarSceneDraw = new LunarSceneDraw();
176 | private World _world;
177 | private float[] _heightField = null;
178 | System.Drawing.Color _terrainColor = System.Drawing.Color.Empty;
179 |
180 |
181 | private TerrainBlock[] _terrain = null;
182 | private int _lastBlockWithData = 0;
183 |
184 | private Body _lander = null;
185 | private List _debris = new List();
186 |
187 | private bool _left = false;
188 | private bool _right = false;
189 | private bool _down = false;
190 |
191 | public LunarSim()
192 | {
193 | }
194 |
195 | public void Dispose()
196 | {
197 | Dispose(true);
198 | }
199 |
200 | protected virtual void Dispose(bool state)
201 | {
202 | if (state)
203 | {
204 | // By deleting the world, we delete the bomb, mouse joint, etc.
205 | _world.Dispose();
206 | _world = null;
207 | }
208 | }
209 |
210 | public void Init(System.Windows.Size worldSize)
211 | {
212 | _stats.Init();
213 | _state &= ~GameState.Playing;
214 | _debugSceneDraw.Flags = DebugDraw.DrawFlags.Shape;
215 | _lunarSceneDraw.Flags = DebugDraw.DrawFlags.Shape;
216 | _worldAABB = new AABB();
217 | _worldAABB.LowerBound.Set(0, 0);
218 | _worldAABB.UpperBound.Set((float)worldSize.Width, (float)worldSize.Height);
219 | Vec2 gravity = new Vec2();
220 | gravity.Set(0.0f, -9.81f);
221 | bool doSleep = false;
222 |
223 | AABB simAABB = _worldAABB;
224 | simAABB.LowerBound.X = -100;
225 | simAABB.UpperBound.X += 200;
226 | simAABB.UpperBound.Y += 200;
227 | _world = new World(simAABB, gravity, doSleep);
228 | _world.SetDebugDraw(_lunarSceneDraw);
229 | _world.SetContactListener(this);
230 | {
231 | // Ground
232 | PolygonDef sd = new PolygonDef();
233 | sd.UserData = ShapeType.Ground;
234 | sd.SetAsBox((float)worldSize.Width, 1);
235 |
236 | BodyDef bd = new BodyDef();
237 | bd.Position.Set(0.0f, 0);
238 | bd.UserData = ShapeType.Ground;
239 | Body ground = _world.CreateBody(bd);
240 | ground.CreateFixture(sd);
241 | }
242 | }
243 |
244 | private Body CreateLander(Vec2 pos)
245 | {
246 | float friction = 1;
247 | // Lander
248 | PolygonDef sd = new PolygonDef();
249 | sd.SetAsBox(5, 1, new Vec2(0, -3f), 0);
250 | sd.Density = 5.0f;
251 | sd.Friction = friction;
252 |
253 | // Foundation
254 | BodyDef bd = new BodyDef();
255 | bd.AngularDamping = 0.5f;
256 | bd.Position = pos;
257 | bd.UserData = ShapeType.LanderFoundation;
258 | Body lander = _world.CreateBody(bd);
259 | lander.CreateFixture(sd);
260 |
261 | {
262 | // Cockpit
263 | CircleDef CapsuleDef = new CircleDef();
264 | CapsuleDef.LocalPosition = new Vec2(0, 2f);
265 | CapsuleDef.Radius = 4;
266 | CapsuleDef.Density = 5.0f;
267 | CapsuleDef.Friction = friction;
268 | CapsuleDef.UserData = ShapeType.LanderCockpit;
269 | lander.CreateFixture(CapsuleDef);
270 | }
271 | {
272 | // Left leg
273 | PolygonDef legDef = new PolygonDef();
274 | legDef.SetAsBox(1, 2, new Vec2(-5f, -6f), 0.0f);
275 | legDef.Density = 2.0f;
276 | legDef.Friction = friction;
277 | legDef.UserData = ShapeType.LanderLeftLeg;
278 | lander.CreateFixture(legDef);
279 | }
280 | {
281 | // Right leg
282 | PolygonDef legDef = new PolygonDef();
283 | legDef.SetAsBox(1, 2, new Vec2(5f, -6f), 0.0f);
284 | legDef.Density = 2.0f;
285 | legDef.Friction = friction;
286 | legDef.UserData = ShapeType.LanderRightLeg;
287 | lander.CreateFixture(legDef);
288 | }
289 | lander.SetMassFromShapes();
290 | return lander;
291 | }
292 |
293 | void ExplodeLander()
294 | {
295 | if (_lander != null)
296 | {
297 | Vec2 pos = _stats.CurrentLocation;
298 | int maxOffset = 10;
299 | _world.DestroyBody(_lander);
300 | _lander = null;
301 | _state &= ~GameState.Playing;
302 |
303 | Vec2[] BoxSizes = new Vec2[3];
304 | BoxSizes[0] = new Vec2(1, 2);
305 | BoxSizes[1] = new Vec2(1, 2);
306 | BoxSizes[2] = new Vec2(5, 1);
307 |
308 | for (int i = 0; i < 3; i++)
309 | {
310 | int force = 6000;
311 | Vec2 offset = new Vec2(_rnd.Next(-maxOffset, maxOffset), _rnd.Next(0, maxOffset));
312 | var box = AddBox(new System.Windows.Point(pos.X + offset.X, pos.Y + offset.Y), BoxSizes[i].X, BoxSizes[i].Y);
313 | _debris.Add(new Debris(_world, box, 5 + 0.1f * _rnd.Next(-10, 10)));
314 | box.ApplyImpulse(new Vec2(_rnd.Next(-force, force), _rnd.Next(-force, force)), pos);
315 | }
316 |
317 | for (int i = 0; i < 7; i++)
318 | {
319 | int force = 6000;
320 | Vec2 offset = new Vec2(_rnd.Next(-maxOffset, maxOffset), _rnd.Next(0, maxOffset));
321 | var box = AddBox(new System.Windows.Point(pos.X + offset.X, pos.Y + offset.Y));
322 | _debris.Add(new Debris(_world, box, 5 + 0.1f * _rnd.Next(-10, 10)));
323 | box.ApplyImpulse(new Vec2(_rnd.Next(-force, force), _rnd.Next(-force, force)), pos);
324 | }
325 | }
326 | }
327 |
328 | void WinTheGame()
329 | {
330 | if (!_stats.Success && _lander != null)
331 | {
332 | _stats.Success = true;
333 | _lander.SetStatic();
334 | _state &= ~GameState.Playing;
335 | }
336 | }
337 |
338 | public Body Lander
339 | {
340 | get { return _lander; }
341 | }
342 |
343 | public GameState State
344 | {
345 | get { return _state; }
346 | }
347 |
348 | public GameStats Stats
349 | {
350 | get { return _stats; }
351 | }
352 |
353 | public void SetDebug(bool debug)
354 | {
355 | _debugMode = debug;
356 | }
357 |
358 | public void SetActive(bool active)
359 | {
360 | if (active)
361 | _state |= GameState.Active;
362 | else
363 | _state &= ~GameState.Active;
364 | }
365 |
366 | public void SetHeightField(float[] heightField, System.Drawing.Color terrainColor)
367 | {
368 | _terrainColor = terrainColor;
369 | if (_heightField == null)
370 | {
371 | _heightField = new float[heightField.Count()];
372 | }
373 |
374 | int blockSize = 5;
375 | int blocks = (heightField.Count() - 1) / blockSize; // Minus 1 so we can always access the next block's first element.
376 | if (_terrain == null || _terrain.Count() != blocks)
377 | {
378 | if (_terrain != null)
379 | {
380 | _terrain.ToList().ForEach(s => { s.Clear(_world); });
381 | }
382 | _terrain = new TerrainBlock[blocks];
383 | for (int i = 0; i < blocks; i++)
384 | _terrain[i] = new TerrainBlock(i, blockSize);
385 | }
386 |
387 | for (int i = 0; i < blocks; ++i)
388 | {
389 | bool dirty = false;
390 | // Each block ends where the next starts so the block actually contains blockSize + 1 vertices or blockSize edges.
391 | for (int p = 0; p <= blockSize; ++p)
392 | {
393 | if (System.Math.Abs(_heightField[i * blockSize + p] - heightField[i * blockSize + p]) > 0.001)
394 | {
395 | dirty = true;
396 | _lastBlockWithData = i;
397 | break;
398 | }
399 | }
400 |
401 | if (dirty)
402 | {
403 | _terrain[i].Update(_world, _worldAABB, new ArraySegment(heightField, i * blockSize, blockSize + 1));
404 | }
405 | }
406 |
407 | _heightField = heightField;
408 | }
409 |
410 | public Body AddBox(System.Windows.Point p, float width = 2.5f, float height = 2.5f)
411 | {
412 | PolygonDef sd = new PolygonDef();
413 | sd.SetAsBox(width, height);
414 | sd.Density = 5.0f;
415 | sd.UserData = ShapeType.Debris;
416 |
417 | Vec2 pos = new Vec2((float)p.X, (float)p.Y);
418 |
419 | BodyDef bd = new BodyDef();
420 | bd.Position = pos;
421 | bd.UserData = ShapeType.Debris;
422 | Body body = _world.CreateBody(bd);
423 | body.CreateFixture(sd);
424 | body.SetMassFromShapes();
425 | return body;
426 | }
427 |
428 | public void Input(bool left, bool right, bool down)
429 | {
430 | if (!_state.HasFlag(GameState.Active) || _lander == null && _stats.Fuel > 0 || _stats.Dead || _stats.Success || _stats.Fuel <= 0)
431 | {
432 | _left = false;
433 | _right = false;
434 | _down = false;
435 | return;
436 | }
437 | _left = left;
438 | _right = right;
439 | _down = down;
440 |
441 | float forceUp = 6000.0f;
442 | float forceSide = 1000.0f;
443 | if (down)
444 | {
445 | var downVec = _lander.GetWorldVector(new Vec2(0, 1));
446 | var center = _lander.GetLocalCenter() + _lander.GetPosition();
447 | _lander.ApplyForce(downVec * forceUp, center);
448 | _stats.Fuel = System.Math.Max(0, _stats.Fuel - _stats.LastStep * 100);
449 |
450 |
451 | }
452 |
453 | if (left || right)
454 | {
455 | var center = _lander.GetLocalCenter() + _lander.GetPosition();
456 | center.Y += 5.0f;
457 | float force = forceSide;
458 | if (left)
459 | force = -force;
460 | var forceVec = _lander.GetWorldVector(new Vec2(1, 0));
461 | _lander.ApplyForce(new Vec2(force, 0), center);
462 | _stats.Fuel = System.Math.Max(0, _stats.Fuel - _stats.LastStep * 25);
463 | }
464 | }
465 |
466 | public void Reset()
467 | {
468 | if (_terrain == null)
469 | {
470 | return;
471 | }
472 | if (_lander != null)
473 | {
474 | _world.DestroyBody(_lander);
475 | }
476 | int startIndex = _rnd.Next(0, _lastBlockWithData);
477 | _lander = CreateLander(new Vec2(_terrain[startIndex].GetPointOfInterest().X, 90f));
478 |
479 | _stats.Init();
480 | int targetIndex = _rnd.Next(0, _lastBlockWithData);
481 | _stats.TargetLocation = _terrain[targetIndex].GetPointOfInterest();
482 | _state |= GameState.Playing;
483 | }
484 |
485 | public void Step(float step, System.Windows.Media.DrawingContext drawingContext)
486 | {
487 | if (!_state.HasFlag(GameState.Active))
488 | return;
489 |
490 | _lunarSceneDraw.DrawingContext = drawingContext;
491 | _debugSceneDraw.DrawingContext = drawingContext;
492 | _draw.DrawingContext = drawingContext;
493 |
494 | _lunarSceneDraw.OverrideColor = new Box2DX.Dynamics.Color(_terrainColor.R / 255.0f, _terrainColor.G / 255.0f, _terrainColor.B / 255.0f);
495 | _world.SetDebugDraw(_debugMode ? (DebugDraw)_debugSceneDraw : (DebugDraw)_lunarSceneDraw);
496 |
497 | _world.SetWarmStarting(true);
498 | _world.SetContinuousPhysics(true);
499 |
500 | _world.Step(step, 10, 8);
501 |
502 | _world.Validate();
503 |
504 | _stats.LastStep = step;
505 | _stats.Speed = Lander != null ? Lander.GetLinearVelocity().Length() : 0;
506 | _stats.WorldTime += step;
507 |
508 |
509 | for (int i = _debris.Count - 1; i >= 0; i--)
510 | {
511 | if (_debris[i].Step(step))
512 | {
513 | _debris.RemoveAt(i);
514 | }
515 | }
516 |
517 | // Evaluate game state.
518 | if (_stats.Dead)
519 | {
520 | ExplodeLander();
521 | }
522 | else
523 | {
524 | if (!_stats.Dead && (_state & GameState.Playing) != 0)
525 | {
526 | // Draw target location marker
527 | float width = (float)System.Math.Sin(_stats.WorldTime);
528 | Vec2[] points = new Vec2[3];
529 | points[0] = _stats.TargetLocation;
530 | points[1] = _stats.TargetLocation;
531 | points[1].X += 10 * width;
532 | points[1].Y += 10;
533 | points[2] = _stats.TargetLocation;
534 | points[2].X -= 10 * width;
535 | points[2].Y += 10;
536 | _draw.DrawPolygon(points, 3, new Box2DX.Dynamics.Color(_terrainColor.R / 255.0f, _terrainColor.G / 255.0f, _terrainColor.B / 255.0f));
537 | }
538 |
539 | if (_lander != null)
540 | {
541 | _stats.CurrentLocation = _lander.GetPosition();
542 |
543 | Func CreateThruster = transform =>
544 | {
545 | float width = (float)System.Math.Sin(_stats.WorldTime * 100);
546 | Vec2[] points = new Vec2[3];
547 | points[0] = new Vec2(0, 0);
548 | points[1] = new Vec2(0, 0);
549 | points[1].X += 2 * width;
550 | points[1].Y -= 10;
551 | points[2] = new Vec2(0, 0);
552 | points[2].X -= 2 * width;
553 | points[2].Y -= 10;
554 | for (int i = 0; i < points.Length; i++)
555 | {
556 | points[i] = transform.Position + Box2DX.Common.Math.Mul(transform.R, points[i]);
557 | }
558 | return points;
559 | };
560 |
561 | if (_down)
562 | {
563 | XForm transform = new XForm(_lander.GetWorldPoint(new Vec2(0, -5)), _lander.GetXForm().R);
564 | Vec2[] points = CreateThruster(transform);
565 | _draw.DrawPolygon(points, 3, new Box2DX.Dynamics.Color(_terrainColor.R / 255.0f, _terrainColor.G / 255.0f, _terrainColor.B / 255.0f));
566 | }
567 | if (_left)
568 | {
569 | XForm transform = new XForm(_lander.GetWorldPoint(new Vec2(3, 2)), new Mat22(_lander.GetAngle() + 90.0f * (float)System.Math.PI / 180.0f));
570 | Vec2[] points = CreateThruster(transform);
571 | _draw.DrawPolygon(points, 3, new Box2DX.Dynamics.Color(_terrainColor.R / 255.0f, _terrainColor.G / 255.0f, _terrainColor.B / 255.0f));
572 | }
573 | if (_right)
574 | {
575 | XForm transform = new XForm(_lander.GetWorldPoint(new Vec2(-3, 2)), new Mat22(_lander.GetAngle() - 90.0f * (float)System.Math.PI / 180.0f));
576 | Vec2[] points = CreateThruster(transform);
577 | _draw.DrawPolygon(points, 3, new Box2DX.Dynamics.Color(_terrainColor.R / 255.0f, _terrainColor.G / 255.0f, _terrainColor.B / 255.0f));
578 | }
579 | }
580 | if (_stats.LeftLegOnGroud && _stats.RightLegOnGroud)
581 | {
582 | WinTheGame();
583 | }
584 | }
585 | }
586 |
587 | public void Render(System.Windows.Media.DrawingContext drawingContext)
588 | {
589 | if ((_state & GameState.Active) != 0)
590 | {
591 |
592 | if ((_state & GameState.Playing) == 0)
593 | {
594 | if (_stats.Success)
595 | {
596 | int score = _stats.GetScore();
597 | string text;
598 | if (score > 0)
599 | {
600 | text = $"You Won! You score is: {score}";
601 | }
602 | else
603 | {
604 | text = $"Game Over! You were too far away from your target!";
605 | }
606 | LunarSceneDraw.DrawText(drawingContext, text, new System.Windows.Point(3, 3), 12D, _terrainColor);
607 | }
608 | else if (_stats.Dead)
609 | {
610 | string text = "Game Over! Your lander exploded!";
611 | LunarSceneDraw.DrawText(drawingContext, text, new System.Windows.Point(3, 3), 12D, _terrainColor);
612 | }
613 |
614 | string text2 = "restart = 'Space', thrusters = 'arrow keys'";
615 | LunarSceneDraw.DrawText(drawingContext, text2, new System.Windows.Point(3, 16), 12D, _terrainColor);
616 | }
617 | else
618 | {
619 | string text = "Speed: " + (_stats.Speed > 0 ? "+" : "-") + System.Math.Abs(_stats.Speed).ToString("0.00 m/s") + " Fuel: " + _stats.Fuel.ToString("00000.0 L");
620 | LunarSceneDraw.DrawText(drawingContext, text, new System.Windows.Point(3, 3), 12D, _terrainColor);
621 | }
622 | }
623 | else
624 | {
625 | string text = "Paused - click here to play";
626 | LunarSceneDraw.DrawText(drawingContext, text, new System.Windows.Point(6, 6), 15D, _terrainColor);
627 | }
628 | }
629 |
630 | public void BeginContact(Contact contact)
631 | {
632 | if (contact.FixtureA?.UserData is ShapeType && contact.FixtureB?.UserData is ShapeType && !_stats.Dead && !_stats.Success)
633 | {
634 | ShapeType shapeA = (ShapeType)contact.FixtureA.UserData;
635 | ShapeType shapeB = (ShapeType)contact.FixtureB.UserData;
636 | if (shapeA == ShapeType.Ground || shapeA == ShapeType.Debris)
637 | {
638 | var temp = shapeA;
639 | shapeA = shapeB;
640 | shapeB = temp;
641 | }
642 |
643 | switch (shapeA)
644 | {
645 | case ShapeType.LanderCockpit:
646 | case ShapeType.LanderFoundation:
647 | {
648 | _stats.Dead = true;
649 | }
650 | break;
651 | case ShapeType.LanderLeftLeg:
652 | {
653 | if (System.Math.Abs(_stats.Speed) > k_maxSpeed)
654 | {
655 | _stats.Dead = true;
656 | }
657 | else
658 | {
659 | _stats.LeftLegOnGroud = true;
660 | }
661 | }
662 | break;
663 | case ShapeType.LanderRightLeg:
664 | {
665 | if (System.Math.Abs(_stats.Speed) > k_maxSpeed)
666 | {
667 | _stats.Dead = true;
668 | }
669 | else
670 | {
671 | _stats.RightLegOnGroud = true;
672 | }
673 | }
674 | break;
675 | default:
676 | break;
677 | }
678 | }
679 | }
680 |
681 | public void EndContact(Contact contact)
682 | {
683 | if (contact.FixtureA?.UserData is ShapeType && contact.FixtureB?.UserData is ShapeType && !_stats.Dead && !_stats.Success)
684 | {
685 | ShapeType shapeA = (ShapeType)contact.FixtureA.UserData;
686 | ShapeType shapeB = (ShapeType)contact.FixtureB.UserData;
687 | if (shapeA == ShapeType.Ground || shapeA == ShapeType.Debris)
688 | {
689 | var temp = shapeA;
690 | shapeA = shapeB;
691 | shapeB = temp;
692 | }
693 |
694 | switch (shapeA)
695 | {
696 | case ShapeType.LanderCockpit:
697 | case ShapeType.LanderFoundation:
698 | {
699 | _stats.Dead = true;
700 | }
701 | break;
702 | case ShapeType.LanderLeftLeg:
703 | {
704 | if (System.Math.Abs(_stats.Speed) > k_maxSpeed)
705 | {
706 | _stats.Dead = true;
707 | }
708 | else
709 | {
710 | _stats.LeftLegOnGroud = false;
711 | }
712 | }
713 | break;
714 | case ShapeType.LanderRightLeg:
715 | {
716 | if (System.Math.Abs(_stats.Speed) > k_maxSpeed)
717 | {
718 | _stats.Dead = true;
719 | }
720 | else
721 | {
722 | _stats.RightLegOnGroud = false;
723 | }
724 | }
725 | break;
726 | default:
727 | break;
728 | }
729 | }
730 | }
731 |
732 | public void PreSolve(Contact contact, Manifold oldManifold)
733 | {
734 | }
735 |
736 | public void PostSolve(Contact contact, ContactImpulse impulse)
737 | {
738 | }
739 | }
740 | }
741 |
--------------------------------------------------------------------------------