├── .gitignore
├── .gitmodules
├── ArcaeaSim.sln
├── ArcaeaSim
├── App.config
├── ApplicationHelper.cs
├── Arcaea.ico
├── ArcaeaSim (Debug).cmd
├── ArcaeaSim.csproj
├── ArcaeaSimApplication.cs
├── Configuration
│ ├── GlobalizationConfig.cs
│ ├── MainAppConfig.cs
│ └── PluginsConfig.cs
├── Contents
│ ├── Content.mgcb
│ ├── app.config.yml
│ ├── game
│ │ └── The Silence
│ │ │ ├── 2.aff
│ │ │ ├── base.jpg
│ │ │ ├── base.mp3
│ │ │ ├── base_256.jpg
│ │ │ └── 歌曲信息.txt
│ ├── globalization.yml
│ ├── plugins.yml
│ └── res
│ │ ├── arcaea.jpg
│ │ └── translations
│ │ ├── default_millisim.iv.yml
│ │ ├── default_millisim.ja-JP.yml
│ │ └── default_millisim.zh-CN.yml
├── Icon.ico
├── Icon.png
├── Options.cs
├── Program.cs
├── Properties
│ └── AssemblyInfo.cs
├── Subsystems
│ ├── Bvs
│ │ ├── ArcClient.cs
│ │ ├── ArcCommunication.cs
│ │ ├── ArcServer.cs
│ │ └── Models
│ │ │ ├── EditReloadRequestParams.cs
│ │ │ ├── GeneralSimInitializeRequestParams.cs
│ │ │ ├── GeneralSimInitializeResponseResult.cs
│ │ │ ├── GeneralSimLaunchedNotificationParams.cs
│ │ │ └── Proposals
│ │ │ ├── SelectedFormatDescriptor.cs
│ │ │ └── SupportedFormatDescriptor.cs
│ ├── Configuration
│ │ └── ConfigurationHelper.cs
│ ├── Globalization
│ │ └── CultureSpecificInfoHelper.cs
│ ├── Interactive
│ │ ├── KeyEventArgs.cs
│ │ ├── KeyboardStateHandler.cs
│ │ └── MouseCameraControl.cs
│ └── Plugin
│ │ ├── ArcaeaSimPluginManager.cs
│ │ └── PluginSearchMode.cs
├── app.manifest
└── packages.config
├── ArcaeaView
├── ArcaeaView.csproj
├── Components
│ ├── BeatmapChangedEventArgs.cs
│ ├── BeatmapLoader.cs
│ ├── BeatmapLoaderFactory.cs
│ ├── Cameraman.cs
│ ├── CameramanFactory.cs
│ ├── TrackDisplay.cs
│ └── TrackDisplayFactory.cs
├── Configuration
│ ├── BeatmapLoaderConfig.cs
│ └── TrackDisplayConfig.cs
├── Contents
│ ├── config
│ │ ├── beatmap_loader.yml
│ │ └── track_display.yml
│ └── res
│ │ ├── fx
│ │ ├── compile_effects.bat
│ │ └── monogame.fxh
│ │ └── img
│ │ ├── air_input.png
│ │ ├── arc_body.png
│ │ ├── arc_body_hi.png
│ │ ├── arc_cap.png
│ │ ├── bg
│ │ └── base_conflict.jpg
│ │ ├── finish_line.png
│ │ ├── note_dark.png
│ │ ├── note_hold_dark.png
│ │ ├── note_hold_dark_hi.png
│ │ ├── tap_d.png
│ │ ├── track_dark.png
│ │ └── track_lane_divider.png
├── Core
│ └── MathF.cs
├── Extensions
│ ├── ColorExtensions.cs
│ └── Vector3Extensions.cs
├── Properties
│ └── AssemblyInfo.cs
├── Subsystems
│ ├── Rendering
│ │ ├── BottomlessColoredHexahedron.cs
│ │ ├── BottomlessColoredTetrahedron.cs
│ │ ├── BottomlessColoredTriangularPrism.cs
│ │ ├── BottomlessTexturedTetrahedron.cs
│ │ ├── BottomlessTexturedTriangularPrism.cs
│ │ ├── Camera.cs
│ │ ├── ColoredBox.cs
│ │ ├── ColoredHexahedron.cs
│ │ ├── ColoredParallelogram.cs
│ │ ├── ColoredRectangle.cs
│ │ ├── DrawableGeometryMesh.cs
│ │ └── TexturedRectangle.cs
│ └── Scores
│ │ ├── Entities
│ │ ├── ArcColor.cs
│ │ ├── ArcEasing.cs
│ │ ├── ArcNote.cs
│ │ ├── Beatmap.cs
│ │ ├── FloorNote.cs
│ │ ├── LongNote.cs
│ │ ├── NoteBase.cs
│ │ ├── NoteType.cs
│ │ ├── SkyNote.cs
│ │ ├── TimingNote.cs
│ │ └── Track.cs
│ │ ├── IHasTick.cs
│ │ ├── IHasTicks.cs
│ │ ├── IPreviewNote.cs
│ │ ├── IRangedPreviewNote.cs
│ │ └── Visualization
│ │ ├── ArcEasingHelper.cs
│ │ ├── ArcVisualNote.cs
│ │ ├── FloorVisualNote.cs
│ │ ├── IDrawableNote.cs
│ │ ├── LongVisualNote.cs
│ │ ├── NoteEffects.cs
│ │ ├── SkyVisualNote.cs
│ │ ├── StageMetrics.cs
│ │ ├── VisualBeatmap.cs
│ │ └── VisualNoteBase.cs
└── packages.config
├── LICENSE.txt
├── README.md
├── appveyor.yml
└── docs
├── Building.md
└── Starting.md
/.gitignore:
--------------------------------------------------------------------------------
1 | .vs
2 | .idea
3 | _ReSharper.*
4 |
5 | packages
6 |
7 | [Bb]in
8 | [Oo]bj
9 |
10 | *.csproj.user
11 | *.sln.user
12 | *.DotSettings.user
13 |
--------------------------------------------------------------------------------
/.gitmodules:
--------------------------------------------------------------------------------
1 | [submodule "thirdparty/MilliSim"]
2 | path = thirdparty/MilliSim
3 | url = https://github.com/hozuki/MilliSim.git
4 |
--------------------------------------------------------------------------------
/ArcaeaSim.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio 15
4 | VisualStudioVersion = 15.0.27130.2036
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ArcaeaView", "ArcaeaView\ArcaeaView.csproj", "{8647E62C-8760-4EE3-9CDE-0F2D15FA7CB4}"
7 | EndProject
8 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "thirdparty", "thirdparty", "{17D20885-7FF8-40F5-B927-FF731322A011}"
9 | EndProject
10 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OpenMLTD.MilliSim.Runtime", "thirdparty\MilliSim\src\OpenMLTD.MilliSim.Runtime\OpenMLTD.MilliSim.Runtime.csproj", "{2F7ECBA5-AA8B-4AB2-8F84-585352935A1B}"
11 | EndProject
12 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MonoGame.Extended.Overlay", "thirdparty\MilliSim\thirdparty\MonoGame.Extended2\Sources\MonoGame.Extended.Overlay\MonoGame.Extended.Overlay.csproj", "{8A716D2A-DE8D-4886-BF8F-76B6B1A7BB8F}"
13 | EndProject
14 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MonoGame.Extended2", "thirdparty\MilliSim\thirdparty\MonoGame.Extended2\Sources\MonoGame.Extended2\MonoGame.Extended2.csproj", "{C7C3D9DE-41A4-4C21-98FB-336ED38A99D5}"
15 | EndProject
16 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MonoGame.Extended.VideoPlayback", "thirdparty\MilliSim\thirdparty\MonoGame.Extended2\Sources\MonoGame.Extended.VideoPlayback\MonoGame.Extended.VideoPlayback.csproj", "{01835222-222F-4FA1-96CC-45C386CF9492}"
17 | EndProject
18 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SharpAL", "thirdparty\MilliSim\thirdparty\SharpAL\SharpAL\SharpAL.csproj", "{DC0F4960-FF4E-4BF4-878C-F76CF1FDDE58}"
19 | EndProject
20 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OpenMLTD.MilliSim.Extension.Audio.StandardAudioFormats", "thirdparty\MilliSim\src\plugins\OpenMLTD.MilliSim.Extension.Audio.StandardAudioFormats\OpenMLTD.MilliSim.Extension.Audio.StandardAudioFormats.csproj", "{001C2077-8F69-4318-9096-A52C215B4C44}"
21 | EndProject
22 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OpenMLTD.MilliSim.Extension.Components.CoreComponents", "thirdparty\MilliSim\src\plugins\OpenMLTD.MilliSim.Extension.Components.CoreComponents\OpenMLTD.MilliSim.Extension.Components.CoreComponents.csproj", "{16575A6D-F4A1-4910-A112-DB1D62EF6342}"
23 | EndProject
24 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ArcaeaSim", "ArcaeaSim\ArcaeaSim.csproj", "{0CAC6757-EF46-4157-9ED6-08E3FBE591BD}"
25 | EndProject
26 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{56A37D24-2799-4534-97A5-ED97D14232FA}"
27 | ProjectSection(SolutionItems) = preProject
28 | .gitignore = .gitignore
29 | .gitmodules = .gitmodules
30 | appveyor.yml = appveyor.yml
31 | LICENSE.txt = LICENSE.txt
32 | README.md = README.md
33 | EndProjectSection
34 | EndProject
35 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "docs", "docs", "{C268C104-0856-4563-BE11-9C7CCB62180E}"
36 | ProjectSection(SolutionItems) = preProject
37 | docs\Building.md = docs\Building.md
38 | docs\Starting.md = docs\Starting.md
39 | EndProjectSection
40 | EndProject
41 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OpenMLTD.Piyopiyo", "thirdparty\MilliSim\thirdparty\piyopiyo\src\OpenMLTD.Piyopiyo\OpenMLTD.Piyopiyo.csproj", "{84A82E83-D04F-49B4-AD55-C6F4CC800F05}"
42 | EndProject
43 | Global
44 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
45 | Debug|Any CPU = Debug|Any CPU
46 | Debug|x86 = Debug|x86
47 | Release|Any CPU = Release|Any CPU
48 | Release|x86 = Release|x86
49 | EndGlobalSection
50 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
51 | {8647E62C-8760-4EE3-9CDE-0F2D15FA7CB4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
52 | {8647E62C-8760-4EE3-9CDE-0F2D15FA7CB4}.Debug|Any CPU.Build.0 = Debug|Any CPU
53 | {8647E62C-8760-4EE3-9CDE-0F2D15FA7CB4}.Debug|x86.ActiveCfg = Debug|Any CPU
54 | {8647E62C-8760-4EE3-9CDE-0F2D15FA7CB4}.Debug|x86.Build.0 = Debug|Any CPU
55 | {8647E62C-8760-4EE3-9CDE-0F2D15FA7CB4}.Release|Any CPU.ActiveCfg = Release|Any CPU
56 | {8647E62C-8760-4EE3-9CDE-0F2D15FA7CB4}.Release|Any CPU.Build.0 = Release|Any CPU
57 | {8647E62C-8760-4EE3-9CDE-0F2D15FA7CB4}.Release|x86.ActiveCfg = Release|Any CPU
58 | {8647E62C-8760-4EE3-9CDE-0F2D15FA7CB4}.Release|x86.Build.0 = Release|Any CPU
59 | {2F7ECBA5-AA8B-4AB2-8F84-585352935A1B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
60 | {2F7ECBA5-AA8B-4AB2-8F84-585352935A1B}.Debug|Any CPU.Build.0 = Debug|Any CPU
61 | {2F7ECBA5-AA8B-4AB2-8F84-585352935A1B}.Debug|x86.ActiveCfg = Debug|Any CPU
62 | {2F7ECBA5-AA8B-4AB2-8F84-585352935A1B}.Debug|x86.Build.0 = Debug|Any CPU
63 | {2F7ECBA5-AA8B-4AB2-8F84-585352935A1B}.Release|Any CPU.ActiveCfg = Release|Any CPU
64 | {2F7ECBA5-AA8B-4AB2-8F84-585352935A1B}.Release|Any CPU.Build.0 = Release|Any CPU
65 | {2F7ECBA5-AA8B-4AB2-8F84-585352935A1B}.Release|x86.ActiveCfg = Release|Any CPU
66 | {2F7ECBA5-AA8B-4AB2-8F84-585352935A1B}.Release|x86.Build.0 = Release|Any CPU
67 | {8A716D2A-DE8D-4886-BF8F-76B6B1A7BB8F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
68 | {8A716D2A-DE8D-4886-BF8F-76B6B1A7BB8F}.Debug|Any CPU.Build.0 = Debug|Any CPU
69 | {8A716D2A-DE8D-4886-BF8F-76B6B1A7BB8F}.Debug|x86.ActiveCfg = Debug|Any CPU
70 | {8A716D2A-DE8D-4886-BF8F-76B6B1A7BB8F}.Debug|x86.Build.0 = Debug|Any CPU
71 | {8A716D2A-DE8D-4886-BF8F-76B6B1A7BB8F}.Release|Any CPU.ActiveCfg = Release|Any CPU
72 | {8A716D2A-DE8D-4886-BF8F-76B6B1A7BB8F}.Release|Any CPU.Build.0 = Release|Any CPU
73 | {8A716D2A-DE8D-4886-BF8F-76B6B1A7BB8F}.Release|x86.ActiveCfg = Release|Any CPU
74 | {8A716D2A-DE8D-4886-BF8F-76B6B1A7BB8F}.Release|x86.Build.0 = Release|Any CPU
75 | {C7C3D9DE-41A4-4C21-98FB-336ED38A99D5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
76 | {C7C3D9DE-41A4-4C21-98FB-336ED38A99D5}.Debug|Any CPU.Build.0 = Debug|Any CPU
77 | {C7C3D9DE-41A4-4C21-98FB-336ED38A99D5}.Debug|x86.ActiveCfg = Debug|Any CPU
78 | {C7C3D9DE-41A4-4C21-98FB-336ED38A99D5}.Debug|x86.Build.0 = Debug|Any CPU
79 | {C7C3D9DE-41A4-4C21-98FB-336ED38A99D5}.Release|Any CPU.ActiveCfg = Release|Any CPU
80 | {C7C3D9DE-41A4-4C21-98FB-336ED38A99D5}.Release|Any CPU.Build.0 = Release|Any CPU
81 | {C7C3D9DE-41A4-4C21-98FB-336ED38A99D5}.Release|x86.ActiveCfg = Release|Any CPU
82 | {C7C3D9DE-41A4-4C21-98FB-336ED38A99D5}.Release|x86.Build.0 = Release|Any CPU
83 | {01835222-222F-4FA1-96CC-45C386CF9492}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
84 | {01835222-222F-4FA1-96CC-45C386CF9492}.Debug|Any CPU.Build.0 = Debug|Any CPU
85 | {01835222-222F-4FA1-96CC-45C386CF9492}.Debug|x86.ActiveCfg = Debug|Any CPU
86 | {01835222-222F-4FA1-96CC-45C386CF9492}.Debug|x86.Build.0 = Debug|Any CPU
87 | {01835222-222F-4FA1-96CC-45C386CF9492}.Release|Any CPU.ActiveCfg = Release|Any CPU
88 | {01835222-222F-4FA1-96CC-45C386CF9492}.Release|Any CPU.Build.0 = Release|Any CPU
89 | {01835222-222F-4FA1-96CC-45C386CF9492}.Release|x86.ActiveCfg = Release|Any CPU
90 | {01835222-222F-4FA1-96CC-45C386CF9492}.Release|x86.Build.0 = Release|Any CPU
91 | {DC0F4960-FF4E-4BF4-878C-F76CF1FDDE58}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
92 | {DC0F4960-FF4E-4BF4-878C-F76CF1FDDE58}.Debug|Any CPU.Build.0 = Debug|Any CPU
93 | {DC0F4960-FF4E-4BF4-878C-F76CF1FDDE58}.Debug|x86.ActiveCfg = Debug|Any CPU
94 | {DC0F4960-FF4E-4BF4-878C-F76CF1FDDE58}.Debug|x86.Build.0 = Debug|Any CPU
95 | {DC0F4960-FF4E-4BF4-878C-F76CF1FDDE58}.Release|Any CPU.ActiveCfg = Release|Any CPU
96 | {DC0F4960-FF4E-4BF4-878C-F76CF1FDDE58}.Release|Any CPU.Build.0 = Release|Any CPU
97 | {DC0F4960-FF4E-4BF4-878C-F76CF1FDDE58}.Release|x86.ActiveCfg = Release|Any CPU
98 | {DC0F4960-FF4E-4BF4-878C-F76CF1FDDE58}.Release|x86.Build.0 = Release|Any CPU
99 | {001C2077-8F69-4318-9096-A52C215B4C44}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
100 | {001C2077-8F69-4318-9096-A52C215B4C44}.Debug|Any CPU.Build.0 = Debug|Any CPU
101 | {001C2077-8F69-4318-9096-A52C215B4C44}.Debug|x86.ActiveCfg = Debug|Any CPU
102 | {001C2077-8F69-4318-9096-A52C215B4C44}.Debug|x86.Build.0 = Debug|Any CPU
103 | {001C2077-8F69-4318-9096-A52C215B4C44}.Release|Any CPU.ActiveCfg = Release|Any CPU
104 | {001C2077-8F69-4318-9096-A52C215B4C44}.Release|Any CPU.Build.0 = Release|Any CPU
105 | {001C2077-8F69-4318-9096-A52C215B4C44}.Release|x86.ActiveCfg = Release|Any CPU
106 | {001C2077-8F69-4318-9096-A52C215B4C44}.Release|x86.Build.0 = Release|Any CPU
107 | {16575A6D-F4A1-4910-A112-DB1D62EF6342}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
108 | {16575A6D-F4A1-4910-A112-DB1D62EF6342}.Debug|Any CPU.Build.0 = Debug|Any CPU
109 | {16575A6D-F4A1-4910-A112-DB1D62EF6342}.Debug|x86.ActiveCfg = Debug|Any CPU
110 | {16575A6D-F4A1-4910-A112-DB1D62EF6342}.Debug|x86.Build.0 = Debug|Any CPU
111 | {16575A6D-F4A1-4910-A112-DB1D62EF6342}.Release|Any CPU.ActiveCfg = Release|Any CPU
112 | {16575A6D-F4A1-4910-A112-DB1D62EF6342}.Release|Any CPU.Build.0 = Release|Any CPU
113 | {16575A6D-F4A1-4910-A112-DB1D62EF6342}.Release|x86.ActiveCfg = Release|Any CPU
114 | {16575A6D-F4A1-4910-A112-DB1D62EF6342}.Release|x86.Build.0 = Release|Any CPU
115 | {0CAC6757-EF46-4157-9ED6-08E3FBE591BD}.Debug|Any CPU.ActiveCfg = Debug|x86
116 | {0CAC6757-EF46-4157-9ED6-08E3FBE591BD}.Debug|Any CPU.Build.0 = Debug|x86
117 | {0CAC6757-EF46-4157-9ED6-08E3FBE591BD}.Debug|x86.ActiveCfg = Debug|x86
118 | {0CAC6757-EF46-4157-9ED6-08E3FBE591BD}.Debug|x86.Build.0 = Debug|x86
119 | {0CAC6757-EF46-4157-9ED6-08E3FBE591BD}.Release|Any CPU.ActiveCfg = Release|x86
120 | {0CAC6757-EF46-4157-9ED6-08E3FBE591BD}.Release|Any CPU.Build.0 = Release|x86
121 | {0CAC6757-EF46-4157-9ED6-08E3FBE591BD}.Release|x86.ActiveCfg = Release|x86
122 | {0CAC6757-EF46-4157-9ED6-08E3FBE591BD}.Release|x86.Build.0 = Release|x86
123 | {84A82E83-D04F-49B4-AD55-C6F4CC800F05}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
124 | {84A82E83-D04F-49B4-AD55-C6F4CC800F05}.Debug|Any CPU.Build.0 = Debug|Any CPU
125 | {84A82E83-D04F-49B4-AD55-C6F4CC800F05}.Debug|x86.ActiveCfg = Debug|Any CPU
126 | {84A82E83-D04F-49B4-AD55-C6F4CC800F05}.Debug|x86.Build.0 = Debug|Any CPU
127 | {84A82E83-D04F-49B4-AD55-C6F4CC800F05}.Release|Any CPU.ActiveCfg = Release|Any CPU
128 | {84A82E83-D04F-49B4-AD55-C6F4CC800F05}.Release|Any CPU.Build.0 = Release|Any CPU
129 | {84A82E83-D04F-49B4-AD55-C6F4CC800F05}.Release|x86.ActiveCfg = Release|Any CPU
130 | {84A82E83-D04F-49B4-AD55-C6F4CC800F05}.Release|x86.Build.0 = Release|Any CPU
131 | EndGlobalSection
132 | GlobalSection(SolutionProperties) = preSolution
133 | HideSolutionNode = FALSE
134 | EndGlobalSection
135 | GlobalSection(NestedProjects) = preSolution
136 | {2F7ECBA5-AA8B-4AB2-8F84-585352935A1B} = {17D20885-7FF8-40F5-B927-FF731322A011}
137 | {8A716D2A-DE8D-4886-BF8F-76B6B1A7BB8F} = {17D20885-7FF8-40F5-B927-FF731322A011}
138 | {C7C3D9DE-41A4-4C21-98FB-336ED38A99D5} = {17D20885-7FF8-40F5-B927-FF731322A011}
139 | {01835222-222F-4FA1-96CC-45C386CF9492} = {17D20885-7FF8-40F5-B927-FF731322A011}
140 | {DC0F4960-FF4E-4BF4-878C-F76CF1FDDE58} = {17D20885-7FF8-40F5-B927-FF731322A011}
141 | {001C2077-8F69-4318-9096-A52C215B4C44} = {17D20885-7FF8-40F5-B927-FF731322A011}
142 | {16575A6D-F4A1-4910-A112-DB1D62EF6342} = {17D20885-7FF8-40F5-B927-FF731322A011}
143 | {C268C104-0856-4563-BE11-9C7CCB62180E} = {56A37D24-2799-4534-97A5-ED97D14232FA}
144 | {84A82E83-D04F-49B4-AD55-C6F4CC800F05} = {17D20885-7FF8-40F5-B927-FF731322A011}
145 | EndGlobalSection
146 | GlobalSection(ExtensibilityGlobals) = postSolution
147 | SolutionGuid = {073575D6-F3DA-49D6-ABC3-568EBD34E402}
148 | EndGlobalSection
149 | EndGlobal
150 |
--------------------------------------------------------------------------------
/ArcaeaSim/App.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
--------------------------------------------------------------------------------
/ArcaeaSim/ApplicationHelper.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.IO;
3 | using System.Reflection;
4 | using JetBrains.Annotations;
5 | using OpenMLTD.MilliSim.Core;
6 |
7 | namespace Moe.Mottomo.ArcaeaSim {
8 | internal static class ApplicationHelper {
9 |
10 | [NotNull]
11 | internal static string StartupPath => LazyStartupPath.Value;
12 |
13 | [NotNull]
14 | internal static string CodeName => LazyCodeName.Value;
15 |
16 | [NotNull]
17 | private static string GetStartupPath() {
18 | var assembly = Assembly.GetEntryAssembly();
19 | var codeBaseUri = new Uri(assembly.EscapedCodeBase);
20 | var assemblyFilePath = codeBaseUri.LocalPath;
21 | var fileInfo = new FileInfo(assemblyFilePath);
22 |
23 | return fileInfo.DirectoryName ?? Environment.CurrentDirectory;
24 | }
25 |
26 | [CanBeNull]
27 | private static string GetCodeName() {
28 | var assembly = Assembly.GetEntryAssembly();
29 | var attr = assembly.GetCustomAttribute();
30 |
31 | return attr?.CodeName;
32 | }
33 |
34 | [NotNull]
35 | private static readonly Lazy LazyStartupPath = new Lazy(GetStartupPath);
36 |
37 | [NotNull]
38 | private static readonly Lazy LazyCodeName = new Lazy(GetCodeName);
39 |
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/ArcaeaSim/Arcaea.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hozuki/ArcaeaSim2/4780dcc5a284837fb17ae5083e7de93304025c2c/ArcaeaSim/Arcaea.ico
--------------------------------------------------------------------------------
/ArcaeaSim/ArcaeaSim (Debug).cmd:
--------------------------------------------------------------------------------
1 | @ArcaeaSim.exe --debug
2 |
--------------------------------------------------------------------------------
/ArcaeaSim/ArcaeaSimApplication.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Diagnostics;
4 | using JetBrains.Annotations;
5 | using Microsoft.Xna.Framework;
6 | using Microsoft.Xna.Framework.Graphics;
7 | using Microsoft.Xna.Framework.Input;
8 | using Microsoft.Xna.Framework.Media;
9 | using Moe.Mottomo.ArcaeaSim.Components;
10 | using Moe.Mottomo.ArcaeaSim.Configuration;
11 | using Moe.Mottomo.ArcaeaSim.Subsystems.Bvs;
12 | using Moe.Mottomo.ArcaeaSim.Subsystems.Interactive;
13 | using Moe.Mottomo.ArcaeaSim.Subsystems.Plugin;
14 | using OpenMLTD.MilliSim.Configuration;
15 | using OpenMLTD.MilliSim.Core;
16 | using OpenMLTD.MilliSim.Extension.Components.CoreComponents;
17 | using OpenMLTD.MilliSim.Foundation;
18 | using OpenMLTD.MilliSim.Foundation.Extending;
19 | using OpenMLTD.MilliSim.Foundation.Extensions;
20 | using OpenMLTD.MilliSim.Globalization;
21 | using OpenMLTD.MilliSim.Graphics;
22 |
23 | namespace Moe.Mottomo.ArcaeaSim {
24 | ///
25 | ///
26 | /// Arcaea application.
27 | ///
28 | public sealed class ArcaeaSimApplication : BaseGame {
29 |
30 | internal ArcaeaSimApplication([NotNull] Options startupOptions, [NotNull] BasePluginManager pluginManager, [NotNull] ConfigurationStore configurationStore, [NotNull] CultureSpecificInfo cultureSpecificInfo)
31 | : base("Contents", pluginManager) {
32 | StartupOptions = startupOptions;
33 | ConfigurationStore = configurationStore;
34 | CultureSpecificInfo = cultureSpecificInfo;
35 |
36 | ApplyConfiguration();
37 | }
38 |
39 | public override Stage Stage => _stage;
40 |
41 | public override ConfigurationStore ConfigurationStore { get; }
42 |
43 | public override CultureSpecificInfo CultureSpecificInfo { get; }
44 |
45 | internal Options StartupOptions { get; }
46 |
47 | protected override void Initialize() {
48 | AppendComponents();
49 | AppendExtensionComponents();
50 |
51 | CenterWindowAndSetTitle();
52 |
53 | base.Initialize();
54 |
55 | InitializeExtensionComponents();
56 | }
57 |
58 | protected override void LoadContent() {
59 | base.LoadContent();
60 |
61 | var editorServerUri = StartupOptions.EditorServerUri;
62 |
63 | if (!string.IsNullOrWhiteSpace(editorServerUri)) {
64 | _communication = new ArcCommunication(this);
65 |
66 | var b = Uri.TryCreate(editorServerUri, UriKind.RelativeOrAbsolute, out var edServerUri);
67 |
68 | if (!b) {
69 | GameLog.Error("Invalid URI format (in editor_server_uri command line param): {0}", editorServerUri);
70 | }
71 |
72 | _communication.EditorServerUri = edServerUri;
73 |
74 | var simulatorServerPort = StartupOptions.SimulatorServerPort;
75 |
76 | _communication.Server.Start(simulatorServerPort);
77 |
78 | _communication.Client.SendLaunchedNotification();
79 | }
80 | }
81 |
82 | protected override void UnloadContent() {
83 | if (_communication != null) {
84 | _communication.Client.SendSimExitedNotification().Wait(TimeSpan.FromSeconds(2));
85 |
86 | _communication.Server.Stop();
87 |
88 | _communication.Dispose();
89 | }
90 |
91 | base.UnloadContent();
92 | }
93 |
94 | private void ApplyConfiguration() {
95 | var graphicsManager = GraphicsDeviceManager;
96 | var config = ConfigurationStore.Get();
97 |
98 | graphicsManager.PreferredBackBufferWidth = config.Data.Window.Width;
99 | graphicsManager.PreferredBackBufferHeight = config.Data.Window.Height;
100 | graphicsManager.PreferredDepthStencilFormat = DepthFormat.Depth16;
101 | graphicsManager.SynchronizeWithVerticalRetrace = true;
102 | graphicsManager.GraphicsProfile = GraphicsProfile.HiDef;
103 |
104 | IsMouseVisible = true;
105 | }
106 |
107 | private void AppendComponents() {
108 | var keyboardStateHandler = new KeyboardStateHandler(this);
109 |
110 | Components.Add(keyboardStateHandler);
111 |
112 | Components.Add(new MouseCameraControl(this));
113 |
114 | keyboardStateHandler.KeyHold += (s, e) => {
115 | var cam = this.FindSingleElement()?.Camera;
116 |
117 | if (cam == null) {
118 | return;
119 | }
120 |
121 | switch (e.KeyCode) {
122 | case Keys.A:
123 | cam.Strafe(-0.1f);
124 | break;
125 | case Keys.D:
126 | cam.Strafe(0.1f);
127 | break;
128 | case Keys.W:
129 | cam.Walk(0.1f);
130 | break;
131 | case Keys.S:
132 | cam.Walk(-0.1f);
133 | break;
134 | case Keys.Left:
135 | cam.Yaw(0.01f);
136 | break;
137 | case Keys.Right:
138 | cam.Yaw(-0.01f);
139 | break;
140 | case Keys.Up:
141 | cam.Pitch(-0.01f);
142 | break;
143 | case Keys.Down:
144 | cam.Pitch(0.01f);
145 | break;
146 | case Keys.R: {
147 | var camera = this.FindSingleElement()?.Camera;
148 | camera?.Reset();
149 | }
150 | break;
151 | case Keys.F6: {
152 | var syncTimer = this.FindSingleElement();
153 | syncTimer?.Stop();
154 | }
155 | break;
156 | }
157 | };
158 |
159 | keyboardStateHandler.KeyDown += (s, e) => {
160 | switch (e.KeyCode) {
161 | case Keys.Space: {
162 | var audioController = this.FindSingleElement();
163 | var music = audioController?.Music;
164 | var syncTimer = this.FindSingleElement();
165 |
166 | Debug.Assert(syncTimer != null, nameof(syncTimer) + " != null");
167 |
168 | if (music == null) {
169 | break;
170 | }
171 |
172 | var isPlaying = syncTimer.IsRunning;
173 |
174 | if (!isPlaying) {
175 | syncTimer.Start();
176 | } else {
177 | syncTimer.Pause();
178 | }
179 | }
180 | break;
181 | }
182 | };
183 | }
184 |
185 | private void AppendExtensionComponents() {
186 | var stage = new Stage(this, ConfigurationStore);
187 | _stage = stage;
188 |
189 | Components.Add(stage);
190 |
191 | var pluginManager = (ArcaeaSimPluginManager)PluginManager;
192 |
193 | var instantiatedIDList = new List();
194 |
195 | foreach (var factoryID in pluginManager.InstancingFactoryIDs) {
196 | var factory = pluginManager.GetPluginByID(factoryID);
197 | var component = factory?.CreateComponent(this, stage);
198 |
199 | if (component != null) {
200 | stage.Components.Add(component);
201 | instantiatedIDList.Add(factoryID);
202 | }
203 | }
204 |
205 | if (instantiatedIDList.Count > 0) {
206 | GameLog.Debug("Instantiated component factories: {0}", string.Join(", ", instantiatedIDList));
207 | } else {
208 | GameLog.Debug("No component factory instantiated.");
209 | }
210 | }
211 |
212 | // For Direct3D, who fails to center the window on startup.
213 | private void CenterWindowAndSetTitle() {
214 | var windowBounds = Window.ClientBounds;
215 | var displayMode = GraphicsDevice.Adapter.CurrentDisplayMode;
216 |
217 | Window.Position = new Point((displayMode.Width - windowBounds.Width) / 2, (displayMode.Height - windowBounds.Height) / 2);
218 |
219 | var config = ConfigurationStore.Get();
220 |
221 | string songTitle;
222 | if (ConfigurationStore.TryGetValue(out var scoreLoaderConfig)) {
223 | songTitle = scoreLoaderConfig.Data.Title;
224 | } else {
225 | songTitle = null;
226 | }
227 |
228 | var appCodeName = ApplicationHelper.CodeName;
229 | var windowTitle = config.Data.Window.Title;
230 |
231 | if (!string.IsNullOrWhiteSpace(songTitle)) {
232 | windowTitle = songTitle + " - " + windowTitle;
233 | }
234 |
235 | if (!string.IsNullOrWhiteSpace(appCodeName)) {
236 | windowTitle = windowTitle + " (\"" + appCodeName + "\")";
237 | }
238 |
239 | Window.Title = windowTitle;
240 | }
241 |
242 | private void InitializeExtensionComponents() {
243 | var helpOverlay = this.FindSingleElement();
244 |
245 | if (helpOverlay != null) {
246 | helpOverlay.Visible = true;
247 | }
248 |
249 | var syncTimer = this.FindSingleElement();
250 |
251 | Debug.Assert(syncTimer != null, nameof(syncTimer) + " != null");
252 |
253 | syncTimer.StateChanged += (s, e) => {
254 | switch (e.NewState) {
255 | case MediaState.Stopped:
256 | if (helpOverlay != null) {
257 | helpOverlay.Visible = true;
258 | }
259 | break;
260 | case MediaState.Playing:
261 | if (helpOverlay != null) {
262 | helpOverlay.Visible = false;
263 | }
264 | break;
265 | case MediaState.Paused:
266 | if (helpOverlay != null) {
267 | helpOverlay.Visible = true;
268 | }
269 | break;
270 | default:
271 | throw new ArgumentOutOfRangeException();
272 | }
273 | };
274 | }
275 |
276 | private Stage _stage;
277 | [CanBeNull]
278 | private ArcCommunication _communication;
279 |
280 | }
281 | }
282 |
--------------------------------------------------------------------------------
/ArcaeaSim/Configuration/GlobalizationConfig.cs:
--------------------------------------------------------------------------------
1 | namespace Moe.Mottomo.ArcaeaSim.Configuration {
2 | public sealed class GlobalizationConfig {
3 |
4 | public string[] TranslationFiles { get; set; }
5 |
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/ArcaeaSim/Configuration/MainAppConfig.cs:
--------------------------------------------------------------------------------
1 | using OpenMLTD.MilliSim.Configuration.Entities;
2 |
3 | namespace Moe.Mottomo.ArcaeaSim.Configuration {
4 | public sealed class MainAppConfig : ConfigBase {
5 |
6 | public MainAppConfigData Data { get; set; }
7 |
8 | public sealed class MainAppConfigData {
9 |
10 | public WindowConfig Window { get; set; }
11 |
12 | public sealed class WindowConfig {
13 |
14 | public int Width { get; set; }
15 |
16 | public int Height { get; set; }
17 |
18 | public string Title { get; set; }
19 |
20 | }
21 |
22 | }
23 |
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/ArcaeaSim/Configuration/PluginsConfig.cs:
--------------------------------------------------------------------------------
1 | using JetBrains.Annotations;
2 | using Moe.Mottomo.ArcaeaSim.Subsystems.Plugin;
3 |
4 | namespace Moe.Mottomo.ArcaeaSim.Configuration {
5 | public sealed class PluginsConfig {
6 |
7 | public PluginsLoading Loading { get; set; }
8 |
9 | public string[] ComponentFactories { get; set; }
10 |
11 | [UsedImplicitly(ImplicitUseKindFlags.InstantiatedWithFixedConstructorSignature)]
12 | public sealed class PluginsLoading {
13 |
14 | public PluginSearchMode Mode { get; set; }
15 |
16 | public PluginsLoadingLists Lists { get; set; }
17 |
18 | [UsedImplicitly(ImplicitUseKindFlags.InstantiatedWithFixedConstructorSignature)]
19 | public sealed class PluginsLoadingLists {
20 |
21 | public string[] WhiteList { get; set; }
22 |
23 | public string[] BlackList { get; set; }
24 |
25 | }
26 |
27 | }
28 |
29 | }
30 | }
--------------------------------------------------------------------------------
/ArcaeaSim/Contents/Content.mgcb:
--------------------------------------------------------------------------------
1 |
2 | #----------------------------- Global Properties ----------------------------#
3 |
4 | /outputDir:bin/$(Platform)
5 | /intermediateDir:obj/$(Platform)
6 | /platform:Windows
7 | /config:
8 | /profile:Reach
9 | /compress:False
10 |
11 | #-------------------------------- References --------------------------------#
12 |
13 |
14 | #---------------------------------- Content ---------------------------------#
15 |
16 |
--------------------------------------------------------------------------------
/ArcaeaSim/Contents/app.config.yml:
--------------------------------------------------------------------------------
1 | # @include
2 |
3 | # Notes:
4 | # 1. Please save this configuration file with UTF-8 encoding without BOM, codepage 65001.
5 | # 2. Numeric values in 'layout' section can appear in 2 forms, integer (interpreted as pixels)
6 | # or percentage (calculated based on window size).
7 | # 3. MilliSim uses BGM time as the standard time. If BGM is not loaded, uses BGA instead. If neither is loaded, uses game time.
8 | metadata:
9 | type: Moe.Mottomo.ArcaeaSim.Configuration.MainAppConfig
10 | assembly_file: ArcaeaSim.exe
11 | for: "@main"
12 | version: 1 # format version
13 | data:
14 | window:
15 | width: 800 # width of the window's client area, in pixels
16 | height: 600 # height of the window's client area, in pixels
17 | title: "ArcaeaSim" # main title of the window
18 |
--------------------------------------------------------------------------------
/ArcaeaSim/Contents/game/The Silence/base.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hozuki/ArcaeaSim2/4780dcc5a284837fb17ae5083e7de93304025c2c/ArcaeaSim/Contents/game/The Silence/base.jpg
--------------------------------------------------------------------------------
/ArcaeaSim/Contents/game/The Silence/base.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hozuki/ArcaeaSim2/4780dcc5a284837fb17ae5083e7de93304025c2c/ArcaeaSim/Contents/game/The Silence/base.mp3
--------------------------------------------------------------------------------
/ArcaeaSim/Contents/game/The Silence/base_256.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hozuki/ArcaeaSim2/4780dcc5a284837fb17ae5083e7de93304025c2c/ArcaeaSim/Contents/game/The Silence/base_256.jpg
--------------------------------------------------------------------------------
/ArcaeaSim/Contents/game/The Silence/歌曲信息.txt:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hozuki/ArcaeaSim2/4780dcc5a284837fb17ae5083e7de93304025c2c/ArcaeaSim/Contents/game/The Silence/歌曲信息.txt
--------------------------------------------------------------------------------
/ArcaeaSim/Contents/globalization.yml:
--------------------------------------------------------------------------------
1 | translation_files:
2 | - res/translations/**/*.iv.yml
3 |
--------------------------------------------------------------------------------
/ArcaeaSim/Contents/plugins.yml:
--------------------------------------------------------------------------------
1 | loading:
2 | mode: default # default, white_list, black_list
3 | lists:
4 | # "default": Search every .dll file in the root path and plugin paths.
5 | # "white_list": ONLY plugins in white list will be loaded
6 | white_list: [ ]
7 | # "black_list": All plugins in root and "plugins" directory EXCEPT those in black list will be loaded
8 | black_list: [ ]
9 | # file paths
10 | # - xxx.dll
11 | component_factories:
12 | # the latter, the closer to the top
13 | # core: base
14 | - plugin.component_factory.sync_timer
15 | - plugin.component_factory.background_music
16 | # core: visual backgrounds
17 | - plugin.component_factory.background_video
18 | - plugin.component_factory.background_image
19 | # score-related
20 | - plugin.component_factory.arcaea.beatmap_loader
21 | - plugin.component_factory.arcaea.cameraman
22 | - plugin.component_factory.arcaea.track_display
23 | # overlays
24 | - plugin.component_factory.help_overlay
25 | - plugin.component_factory.fps_overlay
26 | - plugin.component_factory.debug_overlay
27 | - plugin.component_factory.sync_timer_overlay
28 |
--------------------------------------------------------------------------------
/ArcaeaSim/Contents/res/arcaea.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hozuki/ArcaeaSim2/4780dcc5a284837fb17ae5083e7de93304025c2c/ArcaeaSim/Contents/res/arcaea.jpg
--------------------------------------------------------------------------------
/ArcaeaSim/Contents/res/translations/default_millisim.iv.yml:
--------------------------------------------------------------------------------
1 | system_ui:
2 | help:
3 | press_space_to_start: Press space to start
4 |
--------------------------------------------------------------------------------
/ArcaeaSim/Contents/res/translations/default_millisim.ja-JP.yml:
--------------------------------------------------------------------------------
1 | system_ui:
2 | help:
3 | press_space_to_start: スペースキーでスタート
4 |
--------------------------------------------------------------------------------
/ArcaeaSim/Contents/res/translations/default_millisim.zh-CN.yml:
--------------------------------------------------------------------------------
1 | system_ui:
2 | help:
3 | press_space_to_start: 按空格键开始
4 |
--------------------------------------------------------------------------------
/ArcaeaSim/Icon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hozuki/ArcaeaSim2/4780dcc5a284837fb17ae5083e7de93304025c2c/ArcaeaSim/Icon.ico
--------------------------------------------------------------------------------
/ArcaeaSim/Icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hozuki/ArcaeaSim2/4780dcc5a284837fb17ae5083e7de93304025c2c/ArcaeaSim/Icon.png
--------------------------------------------------------------------------------
/ArcaeaSim/Options.cs:
--------------------------------------------------------------------------------
1 | using CommandLine;
2 |
3 | namespace Moe.Mottomo.ArcaeaSim {
4 | public sealed class Options {
5 |
6 | [Option("debug", HelpText = "Enable debug mode", Required = false, Default = false)]
7 | public bool IsDebugEnabled { get; set; }
8 |
9 | [Option("editor_server_uri", HelpText = "BVSP editor server URI", Required = false, Default = null)]
10 | public string EditorServerUri { get; set; }
11 |
12 | [Option("bvsp_port", HelpText = "BVSP simulator server port override", Required = false, Default = 0)]
13 | public int SimulatorServerPort { get; set; }
14 |
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/ArcaeaSim/Program.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Diagnostics;
3 | using CommandLine;
4 | using JetBrains.Annotations;
5 | using Moe.Mottomo.ArcaeaSim.Subsystems.Configuration;
6 | using Moe.Mottomo.ArcaeaSim.Subsystems.Globalization;
7 | using Moe.Mottomo.ArcaeaSim.Subsystems.Plugin;
8 | using OpenMLTD.MilliSim.Core;
9 | using OpenMLTD.MilliSim.Foundation;
10 | using OpenMLTD.MilliSim.Graphics;
11 |
12 | namespace Moe.Mottomo.ArcaeaSim {
13 | #if WINDOWS || LINUX
14 | ///
15 | /// The main program class.
16 | ///
17 | internal static class Program {
18 |
19 | ///
20 | /// The main entry point for the application.
21 | ///
22 | [STAThread]
23 | private static int Main([NotNull, ItemNotNull] string[] args) {
24 | BaseGame.GraphicsBackend = GraphicsBackend.Direct3D11;
25 |
26 | GameLog.Initialize("arcaea-debug");
27 | GameLog.Enabled = true;
28 |
29 | var exitCode = -1;
30 |
31 | var parser = new Parser(settings => {
32 | settings.IgnoreUnknownArguments = true;
33 | settings.CaseInsensitiveEnumValues = true;
34 | });
35 |
36 | var optionsParsingResult = parser.ParseArguments(args);
37 |
38 | try {
39 | if (optionsParsingResult.Tag == ParserResultType.Parsed) {
40 | var options = ((Parsed)optionsParsingResult).Value;
41 |
42 | // Enable game log if the app is launched with "--debug" switch.
43 | GameLog.Enabled = options.IsDebugEnabled;
44 |
45 | using (var pluginManager = new ArcaeaSimPluginManager()) {
46 | pluginManager.LoadPlugins();
47 |
48 | var configurationStore = ConfigurationHelper.CreateConfigurationStore(pluginManager);
49 | var cultureSpecificInfo = CultureSpecificInfoHelper.CreateCultureSpecificInfo();
50 |
51 | using (var game = new ArcaeaSimApplication(options, pluginManager, configurationStore, cultureSpecificInfo)) {
52 | game.Run();
53 | }
54 |
55 | exitCode = 0;
56 | }
57 | } else {
58 | var helpText = CommandLine.Text.HelpText.AutoBuild(optionsParsingResult);
59 |
60 | GameLog.Info(helpText);
61 | }
62 | } catch (Exception ex) {
63 | GameLog.Error(ex.Message);
64 | GameLog.Error(ex.StackTrace);
65 | Debug.Print(ex.ToString());
66 | }
67 |
68 | return exitCode;
69 | }
70 |
71 | }
72 | #endif
73 | }
74 |
--------------------------------------------------------------------------------
/ArcaeaSim/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Reflection;
3 | using System.Runtime.InteropServices;
4 | using log4net.Config;
5 |
6 | // General Information about an assembly is controlled through the following
7 | // set of attributes. Change these attribute values to modify the information
8 | // associated with an assembly.
9 | [assembly: AssemblyTitle("ArcaeaSim")]
10 | [assembly: AssemblyProduct("ArcaeaSim")]
11 | [assembly: AssemblyConfiguration("")]
12 | [assembly: AssemblyDescription("Arcaea Simulator")]
13 | [assembly: AssemblyCompany("")]
14 | [assembly: AssemblyCopyright("Copyright © 2018")]
15 | [assembly: AssemblyTrademark("")]
16 | [assembly: AssemblyCulture("")]
17 |
18 | // Setting ComVisible to false makes the types in this assembly not visible
19 | // to COM components. If you need to access a type in this assembly from
20 | // COM, set the ComVisible attribute to true on that type.
21 | [assembly: ComVisible(false)]
22 |
23 | // The following GUID is for the ID of the typelib if this project is exposed to COM
24 | [assembly: Guid("395f104a-85df-496b-b533-0dfb62fef8a8")]
25 |
26 | // Version information for an assembly consists of the following four values:
27 | //
28 | // Major Version
29 | // Minor Version
30 | // Build Number
31 | // Revision
32 | //
33 | // You can specify all the values or you can default the Build and Revision Numbers
34 | // by using the '*' as shown below:
35 | // [assembly: AssemblyVersion("1.0.*")]
36 | [assembly: AssemblyVersion("1.0.0.0")]
37 | [assembly: AssemblyFileVersion("1.0.0.0")]
38 |
39 | [assembly: XmlConfigurator]
40 | [assembly: CLSCompliant(true)]
41 |
--------------------------------------------------------------------------------
/ArcaeaSim/Subsystems/Bvs/ArcClient.cs:
--------------------------------------------------------------------------------
1 | using System.Threading.Tasks;
2 | using JetBrains.Annotations;
3 | using Moe.Mottomo.ArcaeaSim.Subsystems.Bvs.Models;
4 | using OpenMLTD.Piyopiyo;
5 | using OpenMLTD.Piyopiyo.Net.JsonRpc;
6 |
7 | namespace Moe.Mottomo.ArcaeaSim.Subsystems.Bvs {
8 | public sealed class ArcClient : JsonRpcClient {
9 |
10 | internal ArcClient([NotNull] ArcCommunication communication) {
11 | _communication = communication;
12 | }
13 |
14 | public Task SendPlayingNotification() {
15 | return SendNotificationWithEmptyBody(CommonProtocolMethodNames.Preview_Playing);
16 | }
17 |
18 | public Task SendPausedNotification() {
19 | return SendNotificationWithEmptyBody(CommonProtocolMethodNames.Preview_Paused);
20 | }
21 |
22 | public Task SendStoppedNotification() {
23 | return SendNotificationWithEmptyBody(CommonProtocolMethodNames.Preview_Stopped);
24 | }
25 |
26 | public Task SendReloadedNotification() {
27 | return SendNotificationWithEmptyBody(CommonProtocolMethodNames.Edit_Reloaded);
28 | }
29 |
30 | public Task SendLaunchedNotification() {
31 | var serverUri = _communication.EditorServerUri;
32 |
33 | if (serverUri == null) {
34 | return Task.FromResult(0);
35 | }
36 |
37 | var endPoint = _communication.Server.EndPoint;
38 |
39 | var param0Object = new GeneralSimLaunchedNotificationParams {
40 | SimulatorServerUri = $"http://{endPoint.Address}:{endPoint.Port}/"
41 | };
42 |
43 | return SendNotificationAsync(serverUri, CommonProtocolMethodNames.General_SimLaunched, new[] { param0Object });
44 | }
45 |
46 | public Task SendSimExitedNotification() {
47 | return SendNotificationWithEmptyBody(CommonProtocolMethodNames.General_SimExited);
48 | }
49 |
50 | private Task SendNotificationWithEmptyBody([NotNull] string method) {
51 | if (_communication.EditorServerUri == null) {
52 | return Task.FromResult(0);
53 | }
54 |
55 | return SendNotificationAsync(_communication.EditorServerUri, method);
56 | }
57 |
58 | [NotNull]
59 | private readonly ArcCommunication _communication;
60 |
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/ArcaeaSim/Subsystems/Bvs/ArcCommunication.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using JetBrains.Annotations;
3 | using OpenMLTD.MilliSim.Core;
4 |
5 | namespace Moe.Mottomo.ArcaeaSim.Subsystems.Bvs {
6 | public sealed class ArcCommunication : DisposableBase {
7 |
8 | internal ArcCommunication([NotNull] ArcaeaSimApplication game) {
9 | Game = game;
10 | Server = new ArcServer(this);
11 | Client = new ArcClient(this);
12 | }
13 |
14 | [NotNull]
15 | public ArcaeaSimApplication Game { get; }
16 |
17 | [CanBeNull]
18 | public Uri EditorServerUri { get; internal set; }
19 |
20 | [NotNull]
21 | public ArcServer Server { get; }
22 |
23 | [NotNull]
24 | public ArcClient Client { get; }
25 |
26 | protected override void Dispose(bool disposing) {
27 | Server.Dispose();
28 | Client.Dispose();
29 | }
30 |
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/ArcaeaSim/Subsystems/Bvs/ArcServer.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Diagnostics;
3 | using System.IO;
4 | using System.Linq;
5 | using System.Net;
6 | using JetBrains.Annotations;
7 | using Moe.Mottomo.ArcaeaSim.Components;
8 | using Moe.Mottomo.ArcaeaSim.Subsystems.Bvs.Models;
9 | using Moe.Mottomo.ArcaeaSim.Subsystems.Bvs.Models.Proposals;
10 | using OpenMLTD.MilliSim.Extension.Components.CoreComponents;
11 | using OpenMLTD.MilliSim.Foundation.Extensions;
12 | using OpenMLTD.Piyopiyo.Extensions;
13 | using OpenMLTD.Piyopiyo.Net.Contributed;
14 | using OpenMLTD.Piyopiyo.Net.JsonRpc;
15 |
16 | namespace Moe.Mottomo.ArcaeaSim.Subsystems.Bvs {
17 | public sealed class ArcServer : SimulatorServer {
18 |
19 | internal ArcServer([NotNull] ArcCommunication communication) {
20 | _communication = communication;
21 | }
22 |
23 | protected override void OnGeneralSimInitialize(object sender, JsonRpcMethodEventArgs e) {
24 | LogToScreen("Received request: general/simInitialize");
25 |
26 | if (JsonRpcHelper.IsRequestValid(e.ParsedRequestObject, out string errorMessage)) {
27 | var requestObject = JsonRpcHelper.TranslateAsRequest(e.ParsedRequestObject);
28 | var param0 = requestObject.Params[0];
29 |
30 | Debug.Assert(param0 != null, nameof(param0) + " != null");
31 |
32 | var param0Object = param0.ToObject();
33 |
34 | var selectedFormat = SelectFormat(param0Object.SupportedFormats);
35 |
36 | var responseResult = new GeneralSimInitializeResponseResult {
37 | SelectedFormat = selectedFormat
38 | };
39 |
40 | e.Context.RpcOk(responseResult);
41 | } else {
42 | Debug.Print(errorMessage);
43 | e.Context.RpcError(JsonRpcErrorCodes.InvalidRequest, errorMessage);
44 | }
45 | }
46 |
47 | protected override void OnPreviewPlay(object sender, JsonRpcMethodEventArgs e) {
48 | LogToScreen("Received request: preview/play");
49 |
50 | if (JsonRpcHelper.IsRequestValid(e.ParsedRequestObject, out string errorMessage)) {
51 | var syncTimer = _communication.Game.FindSingleElement();
52 |
53 | if (syncTimer == null) {
54 | e.Context.RpcError(JsonRpcErrorCodes.InternalError, "Cannot find the " + nameof(SyncTimer) + " component.", statusCode: HttpStatusCode.InternalServerError);
55 | return;
56 | }
57 |
58 | syncTimer.Start();
59 |
60 | e.Context.RpcOk();
61 |
62 | _communication.Client.SendPlayingNotification();
63 | } else {
64 | Debug.Print(errorMessage);
65 | e.Context.RpcError(JsonRpcErrorCodes.InvalidRequest, errorMessage);
66 | }
67 | }
68 |
69 | protected override void OnPreviewPause(object sender, JsonRpcMethodEventArgs e) {
70 | LogToScreen("Received request: preview/pause");
71 |
72 | if (JsonRpcHelper.IsRequestValid(e.ParsedRequestObject, out string errorMessage)) {
73 | var syncTimer = _communication.Game.FindSingleElement();
74 |
75 | if (syncTimer == null) {
76 | e.Context.RpcError(JsonRpcErrorCodes.InternalError, "Cannot find the " + nameof(SyncTimer) + " component.", statusCode: HttpStatusCode.InternalServerError);
77 | return;
78 | }
79 |
80 | syncTimer.Pause();
81 |
82 | e.Context.RpcOk();
83 |
84 | _communication.Client.SendPausedNotification();
85 | } else {
86 | Debug.Print(errorMessage);
87 | e.Context.RpcError(JsonRpcErrorCodes.InvalidRequest, errorMessage);
88 | }
89 | }
90 |
91 | protected override void OnPreviewStop(object sender, JsonRpcMethodEventArgs e) {
92 | LogToScreen("Received request: preview/stop");
93 |
94 | if (JsonRpcHelper.IsRequestValid(e.ParsedRequestObject, out string errorMessage)) {
95 | var syncTimer = _communication.Game.FindSingleElement();
96 |
97 | if (syncTimer == null) {
98 | e.Context.RpcError(JsonRpcErrorCodes.InternalError, "Cannot find the " + nameof(SyncTimer) + " component.", statusCode: HttpStatusCode.InternalServerError);
99 | return;
100 | }
101 |
102 | syncTimer.Stop();
103 |
104 | e.Context.RpcOk();
105 |
106 | _communication.Client.SendStoppedNotification();
107 | } else {
108 | Debug.Print(errorMessage);
109 | e.Context.RpcError(JsonRpcErrorCodes.InvalidRequest, errorMessage);
110 | }
111 | }
112 |
113 | protected override void OnPreviewGetPlaybackState(object sender, JsonRpcMethodEventArgs e) {
114 | LogToScreen("Received request: preview/getPlaybackState");
115 |
116 | if (JsonRpcHelper.IsRequestValid(e.ParsedRequestObject, out string errorMessage)) {
117 | e.Context.RpcOk();
118 | } else {
119 | Debug.Print(errorMessage);
120 | e.Context.RpcError(JsonRpcErrorCodes.InvalidRequest, errorMessage);
121 | }
122 | }
123 |
124 | protected override void OnPreviewSeekByTime(object sender, JsonRpcMethodEventArgs e) {
125 | LogToScreen("Received request: preview/seekByTime");
126 |
127 | e.Context.RpcErrorNotImplemented();
128 | }
129 |
130 | protected override void OnEditReload(object sender, JsonRpcMethodEventArgs e) {
131 | LogToScreen("Received request: edit/reload");
132 |
133 | if (!JsonRpcHelper.IsRequestValid(e.ParsedRequestObject, out string errorMessage)) {
134 | Debug.Print(errorMessage);
135 | e.Context.RpcError(JsonRpcErrorCodes.InvalidRequest, errorMessage);
136 |
137 | return;
138 | }
139 |
140 | var beatmapLoader = _communication.Game.FindSingleElement();
141 |
142 | if (beatmapLoader == null) {
143 | e.Context.RpcError(JsonRpcErrorCodes.InternalError, "Cannot find the " + nameof(BeatmapLoader) + " component.", statusCode: HttpStatusCode.InternalServerError);
144 | return;
145 | }
146 |
147 | var requestObject = JsonRpcHelper.TranslateAsRequest(e.ParsedRequestObject);
148 | var param0 = requestObject.Params[0];
149 |
150 | Debug.Assert(param0 != null, nameof(param0) + " != null");
151 |
152 | var param0Object = param0.ToObject();
153 |
154 | if (!string.IsNullOrEmpty(param0Object.BeatmapFile)) {
155 | if (File.Exists(param0Object.BeatmapFile)) {
156 | var backgroundMusic = _communication.Game.FindSingleElement();
157 |
158 | if (!string.IsNullOrEmpty(param0Object.BackgroundMusicFile)) {
159 | if (backgroundMusic == null) {
160 | e.Context.RpcError(JsonRpcErrorCodes.InternalError, "Cannot find the " + nameof(BackgroundMusic) + " component.", statusCode: HttpStatusCode.InternalServerError);
161 | return;
162 | }
163 |
164 | backgroundMusic.LoadMusic(param0Object.BackgroundMusicFile);
165 | } else {
166 | backgroundMusic?.LoadMusic(null);
167 | }
168 |
169 | beatmapLoader.Load(param0Object.BeatmapFile);
170 | } else {
171 | LogToScreen($"Not found: {param0Object.BeatmapFile}");
172 | }
173 | }
174 |
175 | e.Context.RpcOk();
176 |
177 | // DO NOT await
178 | _communication.Client.SendReloadedNotification();
179 | }
180 |
181 | private void LogToScreen([NotNull] string message) {
182 | var debug = _communication.Game.FindSingleElement();
183 |
184 | if (debug != null) {
185 | message = "[SimServer] " + message;
186 | debug.AddLine(message);
187 | }
188 | }
189 |
190 | [CanBeNull]
191 | private SelectedFormatDescriptor SelectFormat([NotNull, ItemNotNull] SupportedFormatDescriptor[] supportedFormats) {
192 | SelectedFormatDescriptor selectedFormat = null;
193 |
194 | foreach (var format in supportedFormats) {
195 | var locals = _supportedScoreFileFormats.Where(f => f.Game == format.Game && f.FormatId == format.FormatId);
196 |
197 | foreach (var local in locals) {
198 | if (Array.IndexOf(format.Versions, local.Version) >= 0) {
199 | selectedFormat = local;
200 | break;
201 | }
202 | }
203 | }
204 |
205 | return selectedFormat;
206 | }
207 |
208 | private readonly SelectedFormatDescriptor[] _supportedScoreFileFormats = {
209 | new SelectedFormatDescriptor {Game = "arcaea", FormatId = "aff", Version = "*"},
210 | };
211 |
212 | [NotNull]
213 | private readonly ArcCommunication _communication;
214 |
215 | }
216 | }
217 |
--------------------------------------------------------------------------------
/ArcaeaSim/Subsystems/Bvs/Models/EditReloadRequestParams.cs:
--------------------------------------------------------------------------------
1 | using JetBrains.Annotations;
2 | using Newtonsoft.Json;
3 | using Newtonsoft.Json.Serialization;
4 |
5 | namespace Moe.Mottomo.ArcaeaSim.Subsystems.Bvs.Models {
6 | [JsonObject(NamingStrategyType = typeof(SnakeCaseNamingStrategy))]
7 | public sealed class EditReloadRequestParams {
8 |
9 | [JsonConstructor]
10 | internal EditReloadRequestParams() {
11 | }
12 |
13 | [JsonProperty(Required = Required.AllowNull)]
14 | [CanBeNull]
15 | public string BeatmapFile { get; set; }
16 |
17 | [JsonProperty(Required = Required.Always)]
18 | public int BeatmapIndex { get; set; }
19 |
20 | [JsonProperty(Required = Required.Always)]
21 | public float BeatmapOffset { get; set; }
22 |
23 | [JsonProperty]
24 | [CanBeNull]
25 | public string BackgroundMusicFile { get; set; }
26 |
27 | [JsonProperty]
28 | [CanBeNull]
29 | public string BackgroundVideoFile { get; set; }
30 |
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/ArcaeaSim/Subsystems/Bvs/Models/GeneralSimInitializeRequestParams.cs:
--------------------------------------------------------------------------------
1 | using JetBrains.Annotations;
2 | using Moe.Mottomo.ArcaeaSim.Subsystems.Bvs.Models.Proposals;
3 | using Newtonsoft.Json;
4 | using Newtonsoft.Json.Serialization;
5 |
6 | namespace Moe.Mottomo.ArcaeaSim.Subsystems.Bvs.Models {
7 | [JsonObject(NamingStrategyType = typeof(SnakeCaseNamingStrategy))]
8 | public sealed class GeneralSimInitializeRequestParams {
9 |
10 | [JsonConstructor]
11 | internal GeneralSimInitializeRequestParams() {
12 | }
13 |
14 | [NotNull, ItemNotNull]
15 | public SupportedFormatDescriptor[] SupportedFormats { get; set; }
16 |
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/ArcaeaSim/Subsystems/Bvs/Models/GeneralSimInitializeResponseResult.cs:
--------------------------------------------------------------------------------
1 | using JetBrains.Annotations;
2 | using Moe.Mottomo.ArcaeaSim.Subsystems.Bvs.Models.Proposals;
3 | using Newtonsoft.Json;
4 | using Newtonsoft.Json.Serialization;
5 |
6 | namespace Moe.Mottomo.ArcaeaSim.Subsystems.Bvs.Models {
7 | [JsonObject(NamingStrategyType = typeof(SnakeCaseNamingStrategy))]
8 | public sealed class GeneralSimInitializeResponseResult {
9 |
10 | [JsonConstructor]
11 | internal GeneralSimInitializeResponseResult() {
12 | }
13 |
14 | [CanBeNull]
15 | public SelectedFormatDescriptor SelectedFormat { get; set; }
16 |
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/ArcaeaSim/Subsystems/Bvs/Models/GeneralSimLaunchedNotificationParams.cs:
--------------------------------------------------------------------------------
1 | using Newtonsoft.Json;
2 | using Newtonsoft.Json.Serialization;
3 |
4 | namespace Moe.Mottomo.ArcaeaSim.Subsystems.Bvs.Models {
5 | [JsonObject(NamingStrategyType = typeof(SnakeCaseNamingStrategy))]
6 | public sealed class GeneralSimLaunchedNotificationParams {
7 |
8 | [JsonConstructor]
9 | internal GeneralSimLaunchedNotificationParams() {
10 | }
11 |
12 | [JsonProperty("server_uri")]
13 | public string SimulatorServerUri { get; set; }
14 |
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/ArcaeaSim/Subsystems/Bvs/Models/Proposals/SelectedFormatDescriptor.cs:
--------------------------------------------------------------------------------
1 | using JetBrains.Annotations;
2 | using Newtonsoft.Json;
3 | using Newtonsoft.Json.Linq;
4 | using Newtonsoft.Json.Serialization;
5 |
6 | namespace Moe.Mottomo.ArcaeaSim.Subsystems.Bvs.Models.Proposals {
7 | [JsonObject(NamingStrategyType = typeof(SnakeCaseNamingStrategy))]
8 | public sealed class SelectedFormatDescriptor {
9 |
10 | [NotNull]
11 | public string Game { get; set; }
12 |
13 | [JsonProperty("id")]
14 | [NotNull]
15 | public string FormatId { get; set; }
16 |
17 | [NotNull]
18 | public string Version { get; set; }
19 |
20 | [CanBeNull]
21 | public JToken Extra { get; set; }
22 |
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/ArcaeaSim/Subsystems/Bvs/Models/Proposals/SupportedFormatDescriptor.cs:
--------------------------------------------------------------------------------
1 | using JetBrains.Annotations;
2 | using Newtonsoft.Json;
3 | using Newtonsoft.Json.Linq;
4 | using Newtonsoft.Json.Serialization;
5 |
6 | namespace Moe.Mottomo.ArcaeaSim.Subsystems.Bvs.Models.Proposals {
7 | [JsonObject(NamingStrategyType = typeof(SnakeCaseNamingStrategy))]
8 | public sealed class SupportedFormatDescriptor {
9 |
10 | [NotNull]
11 | public string Game { get; set; }
12 |
13 | [JsonProperty("id")]
14 | [NotNull]
15 | public string FormatId { get; set; }
16 |
17 | [NotNull, ItemNotNull]
18 | public string[] Versions { get; set; }
19 |
20 | [CanBeNull]
21 | public JToken Extra { get; set; }
22 |
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/ArcaeaSim/Subsystems/Configuration/ConfigurationHelper.cs:
--------------------------------------------------------------------------------
1 | using JetBrains.Annotations;
2 | using OpenMLTD.MilliSim.Configuration;
3 | using OpenMLTD.MilliSim.Configuration.Extending;
4 | using OpenMLTD.MilliSim.Foundation;
5 | using YamlDotNet.Serialization;
6 | using YamlDotNet.Serialization.NamingConventions;
7 |
8 | namespace Moe.Mottomo.ArcaeaSim.Subsystems.Configuration {
9 | ///
10 | /// Helper functions about .
11 | ///
12 | internal static class ConfigurationHelper {
13 |
14 | ///
15 | /// Creates a new from default entry file using custom YAML converters.
16 | ///
17 | /// The that contains loaded plugins.
18 | /// Created .
19 | [NotNull]
20 | internal static ConfigurationStore CreateConfigurationStore([NotNull] BasePluginManager pluginManager) {
21 | return CreateConfigurationStore(DefaultConfigurationEntryFile, pluginManager);
22 | }
23 |
24 | ///
25 | /// Creates a new from specified entry file using custom YAML type converters.
26 | ///
27 | /// Path of the entry configuration file.
28 | /// The that contains loaded plugins.
29 | /// Created .
30 | [NotNull]
31 | internal static ConfigurationStore CreateConfigurationStore([NotNull] string entryFilePath, [NotNull] BasePluginManager pluginManager) {
32 | var typeConverterFactories = pluginManager.GetPluginsOfType();
33 |
34 | var deserializerBuilder = new DeserializerBuilder()
35 | .IgnoreUnmatchedProperties()
36 | .WithNamingConvention(new UnderscoredNamingConvention());
37 |
38 | // External converters
39 | foreach (var factory in typeConverterFactories) {
40 | var converter = factory.CreateTypeConverter();
41 | deserializerBuilder.WithTypeConverter(converter);
42 | }
43 |
44 | var deserializer = deserializerBuilder.Build();
45 |
46 | return ConfigurationStore.Load(entryFilePath, deserializer);
47 | }
48 |
49 | private const string DefaultConfigurationEntryFile = "Contents/app.config.yml";
50 |
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/ArcaeaSim/Subsystems/Globalization/CultureSpecificInfoHelper.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Globalization;
3 | using System.IO;
4 | using JetBrains.Annotations;
5 | using Moe.Mottomo.ArcaeaSim.Configuration;
6 | using OpenMLTD.MilliSim.Globalization;
7 | using YamlDotNet.Serialization;
8 | using YamlDotNet.Serialization.NamingConventions;
9 |
10 | namespace Moe.Mottomo.ArcaeaSim.Subsystems.Globalization {
11 | ///
12 | /// Helper functions about .
13 | ///
14 | internal static class CultureSpecificInfoHelper {
15 |
16 | ///
17 | /// Creates a new from default entry file.
18 | ///
19 | /// Created .
20 | [NotNull]
21 | internal static CultureSpecificInfo CreateCultureSpecificInfo() {
22 | return CreateCultureSpecificInfo(DefaultGlobalizationEntryFile);
23 | }
24 |
25 | ///
26 | /// Creates a new from specified entry file.
27 | ///
28 | /// Path of the globalization configuration entry file.
29 | /// Created .
30 | [NotNull]
31 | internal static CultureSpecificInfo CreateCultureSpecificInfo([NotNull] string entryFilePath) {
32 | var info = new CultureSpecificInfo(CultureInfo.CurrentUICulture);
33 |
34 | var deserializer = new DeserializerBuilder()
35 | .IgnoreUnmatchedProperties()
36 | .WithNamingConvention(new UnderscoredNamingConvention())
37 | .Build();
38 |
39 | var globalizationConfigFileInfo = new FileInfo(entryFilePath);
40 | GlobalizationConfig config;
41 |
42 | using (var fileStream = File.Open(globalizationConfigFileInfo.FullName, FileMode.Open, FileAccess.Read, FileShare.Read)) {
43 | using (var reader = new StreamReader(fileStream)) {
44 | config = deserializer.Deserialize(reader);
45 | }
46 | }
47 |
48 | var globalizationConfigBaseDirectory = globalizationConfigFileInfo.Directory;
49 |
50 | if (globalizationConfigBaseDirectory == null) {
51 | throw new ApplicationException("Unexpected: base directory for globalization files is null!");
52 | }
53 |
54 | foreach (var translationFileGlob in config.TranslationFiles) {
55 | info.TranslationManager.AddTranslationsFromGlob(globalizationConfigBaseDirectory, translationFileGlob);
56 | }
57 |
58 | return info;
59 | }
60 |
61 | private const string DefaultGlobalizationEntryFile = "Contents/globalization.yml";
62 |
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/ArcaeaSim/Subsystems/Interactive/KeyEventArgs.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using Microsoft.Xna.Framework.Input;
3 |
4 | namespace Moe.Mottomo.ArcaeaSim.Subsystems.Interactive {
5 | public sealed class KeyEventArgs : EventArgs {
6 |
7 | public KeyEventArgs(Keys keyCode, KeyState oldState, KeyState newState) {
8 | KeyCode = keyCode;
9 | OldState = oldState;
10 | NewState = newState;
11 | }
12 |
13 | public Keys KeyCode { get; }
14 |
15 | public KeyState OldState { get; }
16 |
17 | public KeyState NewState { get; }
18 |
19 | }
20 | }
--------------------------------------------------------------------------------
/ArcaeaSim/Subsystems/Interactive/KeyboardStateHandler.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using Microsoft.Xna.Framework;
3 | using Microsoft.Xna.Framework.Input;
4 |
5 | namespace Moe.Mottomo.ArcaeaSim.Subsystems.Interactive {
6 | internal sealed class KeyboardStateHandler : GameComponent {
7 |
8 | public KeyboardStateHandler(Game game)
9 | : base(game) {
10 | _previousState = Keyboard.GetState();
11 | }
12 |
13 | public event EventHandler KeyDown;
14 |
15 | public event EventHandler KeyUp;
16 |
17 | public event EventHandler KeyHold;
18 |
19 | public override void Update(GameTime gameTime) {
20 | base.Update(gameTime);
21 |
22 | if (!Enabled) {
23 | return;
24 | }
25 |
26 | var state = Keyboard.GetState();
27 |
28 | var oldPressed = _previousState.GetPressedKeys();
29 | var newPressed = state.GetPressedKeys();
30 |
31 | for (var i = 0; i < oldPressed.Length; ++i) {
32 | if (Array.IndexOf(newPressed, oldPressed[i]) < 0) {
33 | var e = new KeyEventArgs(oldPressed[i], KeyState.Down, KeyState.Up);
34 |
35 | KeyUp?.Invoke(this, e);
36 | }
37 | }
38 |
39 | for (var i = 0; i < newPressed.Length; ++i) {
40 | if (Array.IndexOf(oldPressed, newPressed[i]) < 0) {
41 | var e = new KeyEventArgs(newPressed[i], KeyState.Up, KeyState.Down);
42 |
43 | KeyDown?.Invoke(this, e);
44 | } else {
45 | var e = new KeyEventArgs(newPressed[i], KeyState.Down, KeyState.Down);
46 |
47 | KeyHold?.Invoke(this, e);
48 | }
49 | }
50 |
51 | _previousState = state;
52 | }
53 |
54 | private KeyboardState _previousState;
55 |
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/ArcaeaSim/Subsystems/Interactive/MouseCameraControl.cs:
--------------------------------------------------------------------------------
1 | using JetBrains.Annotations;
2 | using Microsoft.Xna.Framework;
3 | using Microsoft.Xna.Framework.Input;
4 | using Moe.Mottomo.ArcaeaSim.Components;
5 | using Moe.Mottomo.ArcaeaSim.Subsystems.Rendering;
6 | using OpenMLTD.MilliSim.Foundation.Extensions;
7 |
8 | namespace Moe.Mottomo.ArcaeaSim.Subsystems.Interactive {
9 | public sealed class MouseCameraControl : GameComponent {
10 |
11 | public MouseCameraControl(Game game)
12 | : base(game) {
13 | }
14 |
15 | public override void Initialize() {
16 | base.Initialize();
17 |
18 | _camera = Game.ToBaseGame().FindSingleElement()?.Camera;
19 | }
20 |
21 | public override void Update(GameTime gameTime) {
22 | base.Update(gameTime);
23 |
24 | if (_camera == null) {
25 | return;
26 | }
27 |
28 | var currentMouseState = Mouse.GetState();
29 |
30 | var lastMouseState = _lastMoustState;
31 | _lastMoustState = currentMouseState;
32 |
33 | // When the user is holding the right key, move our camera.
34 | if (lastMouseState.RightButton == ButtonState.Pressed && currentMouseState.RightButton == ButtonState.Pressed) {
35 | float xDifference = currentMouseState.X - lastMouseState.X;
36 | float yDifference = currentMouseState.Y - lastMouseState.Y;
37 |
38 | _camera.Pitch(RotationSpeed * yDifference);
39 | _camera.Yaw(-RotationSpeed * xDifference);
40 | }
41 | }
42 |
43 | private static readonly float RotationSpeed = MathHelper.ToRadians(0.2f);
44 |
45 | [CanBeNull]
46 | private Camera _camera;
47 | private MouseState _lastMoustState;
48 |
49 | }
50 | }
51 |
52 |
--------------------------------------------------------------------------------
/ArcaeaSim/Subsystems/Plugin/ArcaeaSimPluginManager.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Composition.Hosting;
4 | using System.Diagnostics;
5 | using System.IO;
6 | using System.Linq;
7 | using System.Reflection;
8 | using JetBrains.Annotations;
9 | using Moe.Mottomo.ArcaeaSim.Configuration;
10 | using OpenMLTD.MilliSim.Core;
11 | using OpenMLTD.MilliSim.Foundation;
12 | using YamlDotNet.Serialization;
13 | using YamlDotNet.Serialization.NamingConventions;
14 |
15 | namespace Moe.Mottomo.ArcaeaSim.Subsystems.Plugin {
16 | ///
17 | ///
18 | /// Plugin manager for ArcaeaSim.
19 | ///
20 | internal sealed class ArcaeaSimPluginManager : BasePluginManager {
21 |
22 | ///
23 | /// Creates a new instance of .
24 | ///
25 | internal ArcaeaSimPluginManager() {
26 | AppDomain.CurrentDomain.AssemblyResolve += AppDomain_Resolve;
27 | InstancingFactoryIDs = new string[0];
28 | }
29 |
30 | ///
31 | /// Loaded plugin list. Can be used in diagnostic windows, for example a listing plugin details.
32 | ///
33 | public override IReadOnlyList Plugins => _allPlugins;
34 |
35 | ///
36 | /// The IDs of the factories that will be used for instancing responsible classes.
37 | ///
38 | [NotNull, ItemNotNull]
39 | internal IReadOnlyList InstancingFactoryIDs { get; private set; }
40 |
41 | ///
42 | /// Scan and load plugins according to default plugin list file.
43 | ///
44 | internal void LoadPlugins() {
45 | LoadPlugins(DefaultPluginListFile);
46 | }
47 |
48 | ///
49 | /// Scan and load plugins according to specified plugin list file.
50 | ///
51 | /// Path to the plugin list file.
52 | internal void LoadPlugins([NotNull] string pluginListFilePath) {
53 | var deserializerBuilder = new DeserializerBuilder();
54 | var deserializer = deserializerBuilder
55 | .IgnoreUnmatchedProperties()
56 | .WithNamingConvention(new UnderscoredNamingConvention())
57 | .Build();
58 |
59 | PluginsConfig pluginsConfig;
60 |
61 | using (var fileStream = File.Open(pluginListFilePath, FileMode.Open, FileAccess.Read, FileShare.Read)) {
62 | using (var reader = new StreamReader(fileStream)) {
63 | pluginsConfig = deserializer.Deserialize(reader);
64 | }
65 | }
66 |
67 | InstancingFactoryIDs = pluginsConfig.ComponentFactories;
68 |
69 | string[] filters;
70 |
71 | switch (pluginsConfig.Loading.Mode) {
72 | case PluginSearchMode.Default:
73 | filters = null;
74 | break;
75 | case PluginSearchMode.Exclusive:
76 | filters = pluginsConfig.Loading.Lists.BlackList;
77 | break;
78 | case PluginSearchMode.Inclusive:
79 | filters = pluginsConfig.Loading.Lists.WhiteList;
80 | break;
81 | default:
82 | throw new ArgumentOutOfRangeException();
83 | }
84 |
85 | var startupPath = ApplicationHelper.StartupPath;
86 | var searchPaths = new string[SubSearchPaths.Count + 1];
87 |
88 | searchPaths[0] = startupPath;
89 |
90 | for (var i = 1; i <= SubSearchPaths.Count; ++i) {
91 | searchPaths[i] = Path.Combine(startupPath, SubSearchPaths[i - 1]);
92 | }
93 |
94 | LoadAssemblies(pluginsConfig.Loading.Mode, filters, searchPaths);
95 | }
96 |
97 | protected override void Dispose(bool disposing) {
98 | _extensionContainer?.Dispose();
99 | _extensionContainer = null;
100 | _extensionConfiguration = null;
101 |
102 | if (Plugins != null) {
103 | foreach (var plugin in Plugins) {
104 | if (plugin is IDisposable d) {
105 | d.Dispose();
106 | }
107 | }
108 | }
109 |
110 | AppDomain.CurrentDomain.AssemblyResolve -= AppDomain_Resolve;
111 | }
112 |
113 | private void LoadAssemblies(PluginSearchMode searchMode, [CanBeNull, ItemNotNull] string[] filters, [NotNull, ItemNotNull] params string[] searchPaths) {
114 | if (_extensionContainer != null) {
115 | _extensionContainer.Dispose();
116 | _extensionContainer = null;
117 | }
118 |
119 | var allAssemblies = FindAssemblies(searchMode, filters, searchPaths);
120 | var configuration = new ContainerConfiguration().WithAssemblies(allAssemblies);
121 | var host = configuration.CreateContainer();
122 |
123 | var loadedPlugins = host.GetExports().ToArray();
124 | _allPlugins = loadedPlugins;
125 |
126 | _extensionConfiguration = configuration;
127 | _extensionContainer = host;
128 | }
129 |
130 | private static IReadOnlyList FindAssemblies(PluginSearchMode searchMode, [CanBeNull, ItemNotNull] string[] filters, [NotNull, ItemNotNull] params string[] searchPaths) {
131 | var allAssemblies = new List();
132 | var fullFilters = filters?.Where(f => !string.IsNullOrEmpty(f)).Select(Path.GetFullPath).ToArray() ?? new string[0];
133 |
134 | var isModernWindows = Environment.OSVersion.Platform == PlatformID.Win32NT;
135 |
136 | foreach (var directory in searchPaths) {
137 | if (!Directory.Exists(directory)) {
138 | continue;
139 | }
140 |
141 | var assemblyFileNames = Directory
142 | .EnumerateFiles(directory)
143 | .Where(str => str.ToLowerInvariant().EndsWith(".dll"));
144 |
145 | foreach (var assemblyFileName in assemblyFileNames) {
146 | bool PathEquals(string filter) {
147 | var compareFlag = isModernWindows ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal;
148 | return string.Equals(filter, assemblyFileName, compareFlag);
149 | }
150 |
151 | if (fullFilters.Length > 0) {
152 | switch (searchMode) {
153 | case PluginSearchMode.Default:
154 | break;
155 | case PluginSearchMode.Exclusive:
156 | if (Array.Exists(fullFilters, PathEquals)) {
157 | continue;
158 | }
159 | break;
160 | case PluginSearchMode.Inclusive:
161 | if (!Array.Exists(fullFilters, PathEquals)) {
162 | continue;
163 | }
164 | break;
165 | default:
166 | throw new ArgumentOutOfRangeException(nameof(searchMode), searchMode, null);
167 | }
168 | }
169 |
170 | try {
171 | var assembly = Assembly.LoadFrom(assemblyFileName);
172 | allAssemblies.Add(assembly);
173 | } catch (Exception ex) {
174 | Debug.Print(ex.Message);
175 | }
176 | }
177 | }
178 |
179 | return allAssemblies;
180 | }
181 |
182 | private static Assembly AppDomain_Resolve(object sender, ResolveEventArgs e) {
183 | var currentPath = ApplicationHelper.StartupPath;
184 | var assemblyFileName = e.Name + ".dll";
185 |
186 | var assemblyPath = Path.Combine(currentPath, assemblyFileName);
187 |
188 | // Search the root directory first...
189 | if (File.Exists(assemblyPath)) {
190 | return Assembly.LoadFrom(assemblyPath);
191 | }
192 |
193 | // ... and then the specified sub directories (e.g. "plugins") if they exist.
194 | foreach (var subPath in SubSearchPaths) {
195 | assemblyPath = Path.Combine(currentPath, subPath, assemblyFileName);
196 |
197 | if (File.Exists(assemblyPath)) {
198 | return Assembly.LoadFrom(assemblyFileName);
199 | }
200 | }
201 |
202 | return null;
203 | }
204 |
205 | private static readonly IReadOnlyList SubSearchPaths = new[] {
206 | "plugins"
207 | };
208 |
209 | private const string DefaultPluginListFile = "Contents/plugins.yml";
210 |
211 | private ContainerConfiguration _extensionConfiguration;
212 | private CompositionHost _extensionContainer;
213 | private IReadOnlyList _allPlugins;
214 |
215 | }
216 | }
217 |
--------------------------------------------------------------------------------
/ArcaeaSim/Subsystems/Plugin/PluginSearchMode.cs:
--------------------------------------------------------------------------------
1 | namespace Moe.Mottomo.ArcaeaSim.Subsystems.Plugin {
2 | public enum PluginSearchMode {
3 |
4 | Default = 0,
5 | Exclusive = 1,
6 | Inclusive = 2
7 |
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/ArcaeaSim/app.manifest:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 | true/pm
39 |
40 |
41 |
42 |
43 |
--------------------------------------------------------------------------------
/ArcaeaSim/packages.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/ArcaeaView/Components/BeatmapChangedEventArgs.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using JetBrains.Annotations;
3 | using Moe.Mottomo.ArcaeaSim.Subsystems.Scores.Entities;
4 |
5 | namespace Moe.Mottomo.ArcaeaSim.Components {
6 | public sealed class BeatmapChangedEventArgs : EventArgs {
7 |
8 | internal BeatmapChangedEventArgs([CanBeNull] Beatmap oldBeatmap, [CanBeNull] Beatmap newBeatmap) {
9 | OldBeatmap = oldBeatmap;
10 | NewBeatmap = newBeatmap;
11 | }
12 |
13 | [CanBeNull]
14 | public Beatmap OldBeatmap { get; }
15 |
16 | [CanBeNull]
17 | public Beatmap NewBeatmap { get; }
18 |
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/ArcaeaView/Components/BeatmapLoader.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.IO;
3 | using System.Text;
4 | using JetBrains.Annotations;
5 | using Moe.Mottomo.ArcaeaSim.Configuration;
6 | using Moe.Mottomo.ArcaeaSim.Subsystems.Scores.Entities;
7 | using OpenMLTD.MilliSim.Extension.Components.CoreComponents;
8 | using OpenMLTD.MilliSim.Foundation;
9 | using OpenMLTD.MilliSim.Foundation.Extensions;
10 |
11 | namespace Moe.Mottomo.ArcaeaSim.Components {
12 | ///
13 | /// Arcaea beatmap loader component.
14 | ///
15 | public sealed class BeatmapLoader : BaseGameComponent {
16 |
17 | public BeatmapLoader([NotNull] BaseGame game, [CanBeNull] IBaseGameComponentContainer parent)
18 | : base(game, parent) {
19 | }
20 |
21 | public event EventHandler BeatmapChanged;
22 |
23 | [CanBeNull]
24 | public Beatmap Beatmap { get; private set; }
25 |
26 | ///
27 | /// Loads an AFF beatmap from string.
28 | ///
29 | /// AFF beatmap content string.
30 | public void LoadFrom([NotNull] string text) {
31 | Beatmap.TryParse(text, out var newBeatmap);
32 |
33 | var oldBeatmap = Beatmap;
34 | Beatmap = newBeatmap;
35 |
36 | BeatmapChanged?.Invoke(this, new BeatmapChangedEventArgs(oldBeatmap, newBeatmap));
37 |
38 | var game = Game.ToBaseGame();
39 | var debugOverlay = game.FindSingleElement();
40 |
41 | if (debugOverlay != null) {
42 | if (newBeatmap != null) {
43 | debugOverlay.AddLine($"Loaded beatmap from string.");
44 | } else {
45 | debugOverlay.AddLine($"Failed to load beatmap from string.");
46 | }
47 | }
48 | }
49 |
50 | ///
51 | /// Loads an AFF beatmap from file.
52 | ///
53 | /// Path to the file that contains AFF beatmap data.
54 | public void Load([NotNull] string filePath) {
55 | Beatmap newBeatmap;
56 |
57 | using (var fileStream = File.Open(filePath, FileMode.Open, FileAccess.Read, FileShare.Read)) {
58 | using (var reader = new StreamReader(fileStream, Encoding.UTF8)) {
59 | Beatmap.TryParse(reader, out newBeatmap);
60 | }
61 | }
62 |
63 | var oldBeatmap = Beatmap;
64 | Beatmap = newBeatmap;
65 |
66 | BeatmapChanged?.Invoke(this, new BeatmapChangedEventArgs(oldBeatmap, newBeatmap));
67 |
68 | var game = Game.ToBaseGame();
69 | var debugOverlay = game.FindSingleElement();
70 |
71 | if (debugOverlay != null) {
72 | if (newBeatmap != null) {
73 | debugOverlay.AddLine($"Loaded beatmap from '{filePath}'.");
74 | } else {
75 | debugOverlay.AddLine($"Failed to load beatmap from '{filePath}'.");
76 | }
77 | }
78 | }
79 |
80 | protected override void OnLoadContents() {
81 | base.OnLoadContents();
82 |
83 | var config = ConfigurationStore.Get();
84 |
85 | Load(config.Data.FilePath);
86 | }
87 |
88 | }
89 | }
90 |
91 |
--------------------------------------------------------------------------------
/ArcaeaView/Components/BeatmapLoaderFactory.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using OpenMLTD.MilliSim.Core;
3 | using OpenMLTD.MilliSim.Foundation;
4 | using OpenMLTD.MilliSim.Foundation.Extending;
5 |
6 | namespace Moe.Mottomo.ArcaeaSim.Components {
7 | [MilliSimPlugin(typeof(IBaseGameComponentFactory))]
8 | public sealed class BeatmapLoaderFactory : BaseGameComponentFactory {
9 |
10 | public override string PluginID => "plugin.component_factory.arcaea.beatmap_loader";
11 |
12 | public override string PluginName => "Beatmap Loader Factory";
13 |
14 | public override string PluginDescription => "Arcaea Beatmap Loader Factory";
15 |
16 | public override string PluginAuthor => "hozuki";
17 |
18 | public override Version PluginVersion => MyVersion;
19 |
20 | public override IBaseGameComponent CreateComponent(BaseGame game, IBaseGameComponentContainer parent) {
21 | return new BeatmapLoader(game, parent);
22 | }
23 |
24 | private static readonly Version MyVersion = new Version(1, 0, 0, 0);
25 |
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/ArcaeaView/Components/Cameraman.cs:
--------------------------------------------------------------------------------
1 | using JetBrains.Annotations;
2 | using Microsoft.Xna.Framework;
3 | using Moe.Mottomo.ArcaeaSim.Subsystems.Rendering;
4 | using OpenMLTD.MilliSim.Foundation;
5 |
6 | namespace Moe.Mottomo.ArcaeaSim.Components {
7 | ///
8 | /// The component that holds the main camera.
9 | ///
10 | public sealed class Cameraman : BaseGameComponent {
11 |
12 | public Cameraman([NotNull] BaseGame game, [CanBeNull] IBaseGameComponentContainer parent)
13 | : base(game, parent) {
14 | }
15 |
16 | ///
17 | /// Gets the camera held by this component.
18 | ///
19 | public Camera Camera { get; private set; }
20 |
21 | protected override void OnUpdate(GameTime gameTime) {
22 | base.OnUpdate(gameTime);
23 |
24 | Camera.Update();
25 | }
26 |
27 | protected override void OnInitialize() {
28 | base.OnInitialize();
29 |
30 | var camera = new Camera(Game.GraphicsDevice);
31 |
32 | Camera = camera;
33 |
34 | camera.Reset();
35 | }
36 |
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/ArcaeaView/Components/CameramanFactory.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using OpenMLTD.MilliSim.Core;
3 | using OpenMLTD.MilliSim.Foundation;
4 | using OpenMLTD.MilliSim.Foundation.Extending;
5 |
6 | namespace Moe.Mottomo.ArcaeaSim.Components {
7 | [MilliSimPlugin(typeof(IBaseGameComponentFactory))]
8 | public sealed class CameramanFactory : BaseGameComponentFactory {
9 |
10 | public override string PluginID => "plugin.component_factory.arcaea.cameraman";
11 |
12 | public override string PluginName => "Cameraman Factory";
13 |
14 | public override string PluginDescription => "Cameraman Factory";
15 |
16 | public override string PluginAuthor => "hozuki";
17 |
18 | public override Version PluginVersion => MyVersion;
19 |
20 | public override IBaseGameComponent CreateComponent(BaseGame game, IBaseGameComponentContainer parent) {
21 | return new Cameraman(game, parent);
22 | }
23 |
24 | private static readonly Version MyVersion = new Version(1, 0, 0, 0);
25 |
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/ArcaeaView/Components/TrackDisplayFactory.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using OpenMLTD.MilliSim.Core;
3 | using OpenMLTD.MilliSim.Foundation;
4 | using OpenMLTD.MilliSim.Foundation.Extending;
5 | using OpenMLTD.MilliSim.Graphics;
6 |
7 | namespace Moe.Mottomo.ArcaeaSim.Components {
8 | [MilliSimPlugin(typeof(IBaseGameComponentFactory))]
9 | public sealed class TrackDisplayFactory : BaseGameComponentFactory {
10 |
11 | public override string PluginID => "plugin.component_factory.arcaea.track_display";
12 |
13 | public override string PluginName => "Track Display Factory";
14 |
15 | public override string PluginDescription => "Track Display Factory";
16 |
17 | public override string PluginAuthor => "hozuki";
18 |
19 | public override Version PluginVersion => MyVersion;
20 |
21 | public override IBaseGameComponent CreateComponent(BaseGame game, IBaseGameComponentContainer parent) {
22 | return new TrackDisplay(game, (IVisualContainer)parent);
23 | }
24 |
25 | private static readonly Version MyVersion = new Version(1, 0, 0, 0);
26 |
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/ArcaeaView/Configuration/BeatmapLoaderConfig.cs:
--------------------------------------------------------------------------------
1 | using OpenMLTD.MilliSim.Configuration.Entities;
2 |
3 | namespace Moe.Mottomo.ArcaeaSim.Configuration {
4 | public sealed class BeatmapLoaderConfig : ConfigBase {
5 |
6 | public ScoreLoaderConfigData Data { get; set; }
7 |
8 | public sealed class ScoreLoaderConfigData {
9 |
10 | public string Title { get; set; }
11 |
12 | public string FilePath { get; set; }
13 |
14 | }
15 |
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/ArcaeaView/Configuration/TrackDisplayConfig.cs:
--------------------------------------------------------------------------------
1 | using OpenMLTD.MilliSim.Configuration.Entities;
2 |
3 | namespace Moe.Mottomo.ArcaeaSim.Configuration {
4 | public sealed class TrackDisplayConfig : ConfigBase {
5 |
6 | public TrackDisplayConfigData Data { get; set; }
7 |
8 | public sealed class TrackDisplayConfigData {
9 |
10 | public string PanelTexture { get; set; }
11 |
12 | public string TrackLaneDividerTexture { get; set; }
13 |
14 | public string SkyInputTexture { get; set; }
15 |
16 | public string NoteTexture { get; set; }
17 |
18 | public string NoteHoldTexture { get; set; }
19 |
20 | public string NoteHoldHighlightedTexture { get; set; }
21 |
22 | public string NoteSkyTexture { get; set; }
23 |
24 | public string NoteArcTexture { get; set; }
25 |
26 | }
27 |
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/ArcaeaView/Contents/config/beatmap_loader.yml:
--------------------------------------------------------------------------------
1 | metadata:
2 | type: Moe.Mottomo.ArcaeaSim.Configuration.BeatmapLoaderConfig
3 | assembly_file: ArcaeaView.dll
4 | for: component.score.beatmap_loader
5 | version: 1 # format version
6 | data:
7 | title: "The Silence"
8 | file_path: Contents/game/The Silence/2.aff
9 |
--------------------------------------------------------------------------------
/ArcaeaView/Contents/config/track_display.yml:
--------------------------------------------------------------------------------
1 | metadata:
2 | type: Moe.Mottomo.ArcaeaSim.Configuration.TrackDisplayConfig
3 | assembly_file: ArcaeaView.dll
4 | for: component.score.track_display
5 | version: 1 # format version
6 | data:
7 | panel_texture: Contents/res/img/track_dark.png
8 | track_lane_divider_texture: Contents/res/img/track_lane_divider.png
9 | sky_input_texture: Contents/res/img/air_input.png
10 | note_texture: Contents/res/img/note_dark.png
11 | note_hold_texture: Contents/res/img/note_hold_dark.png
12 | note_hold_highlighted_texture: Contents/res/img/note_hold_dark_hi.png
13 | note_sky_texture: Contents/res/img/tap_d.png
14 | note_arc_texture: Contents/res/img/arc_body.png
15 |
--------------------------------------------------------------------------------
/ArcaeaView/Contents/res/fx/compile_effects.bat:
--------------------------------------------------------------------------------
1 | @FOR %%a IN (*.fx) DO @(
2 | "C:\Program Files (x86)\MSBuild\MonoGame\v3.0\Tools\2MGFX.exe" "%%a" "%%a.ogl.mgfxo" /Profile:OpenGL
3 | "C:\Program Files (x86)\MSBuild\MonoGame\v3.0\Tools\2MGFX.exe" "%%a" "%%a.dx11.mgfxo" /Profile:DirectX_11
4 | )
5 |
--------------------------------------------------------------------------------
/ArcaeaView/Contents/res/fx/monogame.fxh:
--------------------------------------------------------------------------------
1 | #if OPENGL
2 | #define SV_POSITION POSITION
3 | #define VS_SHADERMODEL vs_3_0
4 | #define PS_SHADERMODEL ps_3_0
5 | #else
6 | #define VS_SHADERMODEL vs_4_0_level_9_1
7 | #define PS_SHADERMODEL ps_4_0_level_9_1
8 | #endif
9 |
--------------------------------------------------------------------------------
/ArcaeaView/Contents/res/img/air_input.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hozuki/ArcaeaSim2/4780dcc5a284837fb17ae5083e7de93304025c2c/ArcaeaView/Contents/res/img/air_input.png
--------------------------------------------------------------------------------
/ArcaeaView/Contents/res/img/arc_body.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hozuki/ArcaeaSim2/4780dcc5a284837fb17ae5083e7de93304025c2c/ArcaeaView/Contents/res/img/arc_body.png
--------------------------------------------------------------------------------
/ArcaeaView/Contents/res/img/arc_body_hi.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hozuki/ArcaeaSim2/4780dcc5a284837fb17ae5083e7de93304025c2c/ArcaeaView/Contents/res/img/arc_body_hi.png
--------------------------------------------------------------------------------
/ArcaeaView/Contents/res/img/arc_cap.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hozuki/ArcaeaSim2/4780dcc5a284837fb17ae5083e7de93304025c2c/ArcaeaView/Contents/res/img/arc_cap.png
--------------------------------------------------------------------------------
/ArcaeaView/Contents/res/img/bg/base_conflict.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hozuki/ArcaeaSim2/4780dcc5a284837fb17ae5083e7de93304025c2c/ArcaeaView/Contents/res/img/bg/base_conflict.jpg
--------------------------------------------------------------------------------
/ArcaeaView/Contents/res/img/finish_line.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hozuki/ArcaeaSim2/4780dcc5a284837fb17ae5083e7de93304025c2c/ArcaeaView/Contents/res/img/finish_line.png
--------------------------------------------------------------------------------
/ArcaeaView/Contents/res/img/note_dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hozuki/ArcaeaSim2/4780dcc5a284837fb17ae5083e7de93304025c2c/ArcaeaView/Contents/res/img/note_dark.png
--------------------------------------------------------------------------------
/ArcaeaView/Contents/res/img/note_hold_dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hozuki/ArcaeaSim2/4780dcc5a284837fb17ae5083e7de93304025c2c/ArcaeaView/Contents/res/img/note_hold_dark.png
--------------------------------------------------------------------------------
/ArcaeaView/Contents/res/img/note_hold_dark_hi.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hozuki/ArcaeaSim2/4780dcc5a284837fb17ae5083e7de93304025c2c/ArcaeaView/Contents/res/img/note_hold_dark_hi.png
--------------------------------------------------------------------------------
/ArcaeaView/Contents/res/img/tap_d.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hozuki/ArcaeaSim2/4780dcc5a284837fb17ae5083e7de93304025c2c/ArcaeaView/Contents/res/img/tap_d.png
--------------------------------------------------------------------------------
/ArcaeaView/Contents/res/img/track_dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hozuki/ArcaeaSim2/4780dcc5a284837fb17ae5083e7de93304025c2c/ArcaeaView/Contents/res/img/track_dark.png
--------------------------------------------------------------------------------
/ArcaeaView/Contents/res/img/track_lane_divider.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hozuki/ArcaeaSim2/4780dcc5a284837fb17ae5083e7de93304025c2c/ArcaeaView/Contents/res/img/track_lane_divider.png
--------------------------------------------------------------------------------
/ArcaeaView/Core/MathF.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Runtime.CompilerServices;
3 |
4 | namespace Moe.Mottomo.ArcaeaSim.Core {
5 | ///
6 | /// Floating point number helper functions.
7 | ///
8 | public static class MathF {
9 |
10 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
11 | public static float Cos(float radian) {
12 | return (float)Math.Cos(radian);
13 | }
14 |
15 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
16 | public static float Sin(float radian) {
17 | return (float)Math.Sin(radian);
18 | }
19 |
20 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
21 | public static float Atan(float d) {
22 | return (float)Math.Atan(d);
23 | }
24 |
25 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
26 | public static float Atan2(float y, float x) {
27 | return (float)Math.Atan2(y, x);
28 | }
29 |
30 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
31 | public static float Tan(float radian) {
32 | return (float)Math.Tan(radian);
33 | }
34 |
35 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
36 | public static float Sqrt(float value) {
37 | return (float)Math.Sqrt(value);
38 | }
39 |
40 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
41 | public static float ClampLower(float value, float lowerBound) {
42 | return value < lowerBound ? lowerBound : value;
43 | }
44 |
45 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
46 | public static float ClampUpper(float value, float upperBound) {
47 | return value > upperBound ? upperBound : value;
48 | }
49 |
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/ArcaeaView/Extensions/ColorExtensions.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.Xna.Framework;
2 |
3 | namespace Moe.Mottomo.ArcaeaSim.Extensions {
4 | public static class ColorExtensions {
5 |
6 | ///
7 | /// Return a new color whose brighness is changed from a base color.
8 | ///
9 | /// Base color.
10 | /// A value between [-255, 255]
11 | ///
12 | public static Color ChangeBrightness(this Color color, int amount) {
13 | var a = color.A;
14 | var r = MathHelper.Clamp(color.R + amount, 0, 255);
15 | var g = MathHelper.Clamp(color.G + amount, 0, 255);
16 | var b = MathHelper.Clamp(color.B + amount, 0, 255);
17 |
18 | return new Color(r, g, b, a);
19 | }
20 |
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/ArcaeaView/Extensions/Vector3Extensions.cs:
--------------------------------------------------------------------------------
1 | using System.Runtime.CompilerServices;
2 | using Microsoft.Xna.Framework;
3 |
4 | namespace Moe.Mottomo.ArcaeaSim.Extensions {
5 | public static class Vector3Extensions {
6 |
7 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
8 | public static Vector2 XY(this Vector3 vector) {
9 | return new Vector2(vector.X, vector.Y);
10 | }
11 |
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/ArcaeaView/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Reflection;
3 | using System.Runtime.InteropServices;
4 |
5 | // 有关程序集的一般信息由以下
6 | // 控制。更改这些特性值可修改
7 | // 与程序集关联的信息。
8 | [assembly: AssemblyTitle("ArcaeaView")]
9 | [assembly: AssemblyDescription("Arcaea Simulator Core")]
10 | [assembly: AssemblyConfiguration("")]
11 | [assembly: AssemblyCompany("")]
12 | [assembly: AssemblyProduct("ArcaeaView")]
13 | [assembly: AssemblyCopyright("Copyright © 2018")]
14 | [assembly: AssemblyTrademark("")]
15 | [assembly: AssemblyCulture("")]
16 |
17 | // 将 ComVisible 设置为 false 会使此程序集中的类型
18 | //对 COM 组件不可见。如果需要从 COM 访问此程序集中的类型
19 | //请将此类型的 ComVisible 特性设置为 true。
20 | [assembly: ComVisible(false)]
21 |
22 | // 如果此项目向 COM 公开,则下列 GUID 用于类型库的 ID
23 | [assembly: Guid("8647e62c-8760-4ee3-9cde-0f2d15fa7cb4")]
24 |
25 | // 程序集的版本信息由下列四个值组成:
26 | //
27 | // 主版本
28 | // 次版本
29 | // 生成号
30 | // 修订号
31 | //
32 | // 可以指定所有值,也可以使用以下所示的 "*" 预置版本号和修订号
33 | //通过使用 "*",如下所示:
34 | // [assembly: AssemblyVersion("1.0.*")]
35 | [assembly: AssemblyVersion("1.0.0.0")]
36 | [assembly: AssemblyFileVersion("1.0.0.0")]
37 |
38 | [assembly: CLSCompliant(true)]
39 |
--------------------------------------------------------------------------------
/ArcaeaView/Subsystems/Rendering/BottomlessColoredHexahedron.cs:
--------------------------------------------------------------------------------
1 | using JetBrains.Annotations;
2 | using Microsoft.Xna.Framework;
3 | using Microsoft.Xna.Framework.Graphics;
4 |
5 | namespace Moe.Mottomo.ArcaeaSim.Subsystems.Rendering {
6 | ///
7 | ///
8 | /// Actually front-less and back-less (LOL).
9 | /// Uses .
10 | ///
11 | public sealed class BottomlessColoredHexahedron : DrawableGeometryMesh {
12 |
13 | public BottomlessColoredHexahedron([NotNull] GraphicsDevice graphicsDevice) {
14 | _graphicsDevice = graphicsDevice;
15 |
16 | _vertexBuffer = new VertexBuffer(graphicsDevice, VertexPositionColor.VertexDeclaration, 8, BufferUsage.WriteOnly);
17 | _indexBuffer = new IndexBuffer(graphicsDevice, IndexElementSize.SixteenBits, 24, BufferUsage.WriteOnly);
18 |
19 | var indices = new ushort[] {
20 | // No front and back
21 | //0, 1, 2,
22 | //2, 1, 3,
23 | //4, 5, 6,
24 | //6, 5, 7,
25 |
26 | 0, 1, 4,
27 | 1, 4, 5,
28 | 2, 3, 6,
29 | 3, 6, 7,
30 |
31 | 0, 2, 4,
32 | 2, 4, 6,
33 | 1, 3, 5,
34 | 3, 5, 7
35 | };
36 |
37 | _indexBuffer.SetData(indices);
38 | }
39 |
40 | public override void Draw(EffectTechnique technique) {
41 | _graphicsDevice.SetVertexBuffer(_vertexBuffer);
42 | _graphicsDevice.Indices = _indexBuffer;
43 |
44 | foreach (var pass in technique.Passes) {
45 | pass.Apply();
46 | _graphicsDevice.DrawIndexedPrimitives(PrimitiveType.TriangleList, 0, 0, 8);
47 | }
48 | }
49 |
50 | public void SetVertices(Vector3 start, Vector3 end, Color color, Vector2 sectionSize) {
51 | var vertices = new[] {
52 | new VertexPositionColor {Position = new Vector3(start.X - sectionSize.X / 2, start.Y, start.Z - sectionSize.Y / 2), Color = color},
53 | new VertexPositionColor {Position = new Vector3(start.X + sectionSize.X / 2, start.Y, start.Z - sectionSize.Y / 2), Color = color},
54 | new VertexPositionColor {Position = new Vector3(start.X - sectionSize.X / 2, start.Y, start.Z + sectionSize.Y / 2), Color = color},
55 | new VertexPositionColor {Position = new Vector3(start.X + sectionSize.X / 2, start.Y, start.Z + sectionSize.Y / 2), Color = color},
56 |
57 | new VertexPositionColor {Position = new Vector3(end.X - sectionSize.X / 2, end.Y, end.Z - sectionSize.Y / 2), Color = color},
58 | new VertexPositionColor {Position = new Vector3(end.X + sectionSize.X / 2, end.Y, end.Z - sectionSize.Y / 2), Color = color},
59 | new VertexPositionColor {Position = new Vector3(end.X - sectionSize.X / 2, end.Y, end.Z + sectionSize.Y / 2), Color = color},
60 | new VertexPositionColor {Position = new Vector3(end.X + sectionSize.X / 2, end.Y, end.Z + sectionSize.Y / 2), Color = color}
61 | };
62 |
63 | _vertexBuffer.SetData(vertices);
64 | }
65 |
66 | protected override void Dispose(bool disposing) {
67 | _vertexBuffer.Dispose();
68 | _indexBuffer.Dispose();
69 |
70 | _vertexBuffer = null;
71 | _indexBuffer = null;
72 | }
73 |
74 | private readonly GraphicsDevice _graphicsDevice;
75 | private VertexBuffer _vertexBuffer;
76 | private IndexBuffer _indexBuffer;
77 |
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/ArcaeaView/Subsystems/Rendering/BottomlessColoredTetrahedron.cs:
--------------------------------------------------------------------------------
1 | using JetBrains.Annotations;
2 | using Microsoft.Xna.Framework;
3 | using Microsoft.Xna.Framework.Graphics;
4 |
5 | namespace Moe.Mottomo.ArcaeaSim.Subsystems.Rendering {
6 | ///
7 | ///
8 | /// Actually front-less and back-less (LOL).
9 | /// Uses .
10 | ///
11 | public sealed class BottomlessColoredTetrahedron : DrawableGeometryMesh {
12 |
13 | public BottomlessColoredTetrahedron([NotNull] GraphicsDevice graphicsDevice) {
14 | _graphicsDevice = graphicsDevice;
15 |
16 | _vertexBuffer = new VertexBuffer(graphicsDevice, VertexPositionColor.VertexDeclaration, 4, BufferUsage.WriteOnly);
17 | _indexBuffer = new IndexBuffer(graphicsDevice, IndexElementSize.SixteenBits, 9, BufferUsage.WriteOnly);
18 |
19 | var indices = new ushort[] {
20 | 0, 1, 2,
21 | 0, 1, 3
22 | };
23 |
24 | _indexBuffer.SetData(indices);
25 | }
26 |
27 | public override void Draw(EffectTechnique technique) {
28 | _graphicsDevice.SetVertexBuffer(_vertexBuffer);
29 | _graphicsDevice.Indices = _indexBuffer;
30 |
31 | foreach (var pass in technique.Passes) {
32 | pass.Apply();
33 | _graphicsDevice.DrawIndexedPrimitives(PrimitiveType.TriangleList, 0, 0, 3);
34 | }
35 | }
36 |
37 | public void SetVerticesXZ(Vector3 point, Color color, float rightAngleSideWidth) {
38 | var std = rightAngleSideWidth / 2;
39 | var dx = std;
40 | var dy = std;
41 | var dzDown = std / 2;
42 | var dzUp = dzDown;
43 |
44 | var vertices = new[] {
45 | new VertexPositionColor {Position = new Vector3(point.X, point.Y, point.Z + dzUp), Color = color},
46 | new VertexPositionColor {Position = new Vector3(point.X, point.Y - dy, point.Z - dzDown), Color = color},
47 | new VertexPositionColor {Position = new Vector3(point.X - dx, point.Y, point.Z - dzDown), Color = color},
48 | new VertexPositionColor {Position = new Vector3(point.X + dx, point.Y, point.Z - dzDown), Color = color},
49 | };
50 |
51 | _vertexBuffer.SetData(vertices);
52 | }
53 |
54 | protected override void Dispose(bool disposing) {
55 | _vertexBuffer.Dispose();
56 | _indexBuffer.Dispose();
57 |
58 | _vertexBuffer = null;
59 | _indexBuffer = null;
60 | }
61 |
62 | private readonly GraphicsDevice _graphicsDevice;
63 | private VertexBuffer _vertexBuffer;
64 | private IndexBuffer _indexBuffer;
65 |
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/ArcaeaView/Subsystems/Rendering/BottomlessColoredTriangularPrism.cs:
--------------------------------------------------------------------------------
1 | using JetBrains.Annotations;
2 | using Microsoft.Xna.Framework;
3 | using Microsoft.Xna.Framework.Graphics;
4 | using Moe.Mottomo.ArcaeaSim.Core;
5 |
6 | namespace Moe.Mottomo.ArcaeaSim.Subsystems.Rendering {
7 | ///
8 | ///
9 | /// Uses .
10 | ///
11 | public sealed class BottomlessColoredTriangularPrism : DrawableGeometryMesh {
12 |
13 | public BottomlessColoredTriangularPrism([NotNull] GraphicsDevice graphicsDevice) {
14 | _graphicsDevice = graphicsDevice;
15 |
16 | _vertexBuffer = new VertexBuffer(graphicsDevice, VertexPositionColor.VertexDeclaration, 6, BufferUsage.WriteOnly);
17 | _indexBuffer = new IndexBuffer(graphicsDevice, IndexElementSize.SixteenBits, 12, BufferUsage.WriteOnly);
18 |
19 | var indices = new ushort[] {
20 | // Arcs have only two sides.
21 | 0, 1, 3,
22 | 3, 1, 4,
23 | 0, 2, 3,
24 | 3, 2, 5
25 | };
26 |
27 | _indexBuffer.SetData(indices);
28 | }
29 |
30 | public override void Draw(EffectTechnique technique) {
31 | _graphicsDevice.SetVertexBuffer(_vertexBuffer);
32 | _graphicsDevice.Indices = _indexBuffer;
33 |
34 | foreach (var pass in technique.Passes) {
35 | pass.Apply();
36 | _graphicsDevice.DrawIndexedPrimitives(PrimitiveType.TriangleList, 0, 0, 4);
37 | }
38 | }
39 |
40 | public void SetVerticesXY(Vector3 start, Vector3 end, Color color, float rightAngleSideWidth) {
41 | // Half of the width of the right angle side
42 | var std = rightAngleSideWidth / 2;
43 | var dx = std;
44 | var dzDown = std / 2;
45 | var dzUp = dzDown;
46 |
47 | var vertices = new[] {
48 | new VertexPositionColor {Position = new Vector3(start.X, start.Y, start.Z + dzUp), Color = color},
49 | new VertexPositionColor {Position = new Vector3(start.X - dx, start.Y, start.Z - dzDown), Color = color},
50 | new VertexPositionColor {Position = new Vector3(start.X + dx, start.Y, start.Z - dzDown), Color = color},
51 | new VertexPositionColor {Position = new Vector3(end.X, end.Y, end.Z + dzUp), Color = color},
52 | new VertexPositionColor {Position = new Vector3(end.X - dx, end.Y, end.Z - dzDown), Color = color},
53 | new VertexPositionColor {Position = new Vector3(end.X + dx, end.Y, end.Z - dzDown), Color = color}
54 | };
55 |
56 | _vertexBuffer.SetData(vertices);
57 | }
58 |
59 | protected override void Dispose(bool disposing) {
60 | _vertexBuffer.Dispose();
61 | _indexBuffer.Dispose();
62 |
63 | _vertexBuffer = null;
64 | _indexBuffer = null;
65 | }
66 |
67 | private readonly GraphicsDevice _graphicsDevice;
68 | private VertexBuffer _vertexBuffer;
69 | private IndexBuffer _indexBuffer;
70 |
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/ArcaeaView/Subsystems/Rendering/BottomlessTexturedTetrahedron.cs:
--------------------------------------------------------------------------------
1 | using JetBrains.Annotations;
2 | using Microsoft.Xna.Framework;
3 | using Microsoft.Xna.Framework.Graphics;
4 |
5 | namespace Moe.Mottomo.ArcaeaSim.Subsystems.Rendering {
6 | ///
7 | ///
8 | /// Actually front-less and back-less (LOL).
9 | /// Uses .
10 | ///
11 | public sealed class BottomlessTexturedTetrahedron : DrawableGeometryMesh {
12 |
13 | public BottomlessTexturedTetrahedron([NotNull] GraphicsDevice graphicsDevice) {
14 | _graphicsDevice = graphicsDevice;
15 |
16 | _vertexBuffer = new VertexBuffer(graphicsDevice, VertexPositionColorTexture.VertexDeclaration, 4, BufferUsage.WriteOnly);
17 | _indexBuffer = new IndexBuffer(graphicsDevice, IndexElementSize.SixteenBits, 9, BufferUsage.WriteOnly);
18 |
19 | var indices = new ushort[] {
20 | 0, 1, 2,
21 | 0, 1, 3
22 | };
23 |
24 | _indexBuffer.SetData(indices);
25 | }
26 |
27 | public override void Draw(EffectTechnique technique) {
28 | _graphicsDevice.SetVertexBuffer(_vertexBuffer);
29 | _graphicsDevice.Indices = _indexBuffer;
30 |
31 | foreach (var pass in technique.Passes) {
32 | pass.Apply();
33 | _graphicsDevice.DrawIndexedPrimitives(PrimitiveType.TriangleList, 0, 0, 3);
34 | }
35 | }
36 |
37 | public void SetVerticesXZ(Vector3 point, Color color, float rightAngleSideWidth) {
38 | var std = rightAngleSideWidth / 2;
39 | var dx = std;
40 | var dy = std;
41 | var dzDown = std / 2;
42 | var dzUp = dzDown;
43 |
44 | var vertices = new[] {
45 | new VertexPositionColorTexture {Position = new Vector3(point.X, point.Y, point.Z + dzUp), Color = color, TextureCoordinate = new Vector2(1, 0)},
46 | new VertexPositionColorTexture {Position = new Vector3(point.X, point.Y - dy, point.Z - dzDown), Color = color, TextureCoordinate = new Vector2(0, 1)},
47 | new VertexPositionColorTexture {Position = new Vector3(point.X - dx, point.Y, point.Z - dzDown), Color = color, TextureCoordinate = new Vector2(0, 0)},
48 | new VertexPositionColorTexture {Position = new Vector3(point.X + dx, point.Y, point.Z - dzDown), Color = color, TextureCoordinate = new Vector2(0, 0)},
49 | };
50 |
51 | _vertexBuffer.SetData(vertices);
52 | }
53 |
54 | protected override void Dispose(bool disposing) {
55 | _vertexBuffer.Dispose();
56 | _indexBuffer.Dispose();
57 |
58 | _vertexBuffer = null;
59 | _indexBuffer = null;
60 | }
61 |
62 | private readonly GraphicsDevice _graphicsDevice;
63 | private VertexBuffer _vertexBuffer;
64 | private IndexBuffer _indexBuffer;
65 |
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/ArcaeaView/Subsystems/Rendering/BottomlessTexturedTriangularPrism.cs:
--------------------------------------------------------------------------------
1 | using JetBrains.Annotations;
2 | using Microsoft.Xna.Framework;
3 | using Microsoft.Xna.Framework.Graphics;
4 | using Moe.Mottomo.ArcaeaSim.Core;
5 |
6 | namespace Moe.Mottomo.ArcaeaSim.Subsystems.Rendering {
7 | ///
8 | ///
9 | /// Uses .
10 | ///
11 | public sealed class BottomlessTexturedTriangularPrism : DrawableGeometryMesh {
12 |
13 | public BottomlessTexturedTriangularPrism([NotNull] GraphicsDevice graphicsDevice) {
14 | _graphicsDevice = graphicsDevice;
15 |
16 | _vertexBuffer = new VertexBuffer(graphicsDevice, VertexPositionColorTexture.VertexDeclaration, 6, BufferUsage.WriteOnly);
17 | _indexBuffer = new IndexBuffer(graphicsDevice, IndexElementSize.SixteenBits, 12, BufferUsage.WriteOnly);
18 |
19 | var indices = new ushort[] {
20 | // Arcs have only two sides.
21 | 0, 1, 3,
22 | 3, 1, 4,
23 | 0, 2, 3,
24 | 3, 2, 5
25 | };
26 |
27 | _indexBuffer.SetData(indices);
28 | }
29 |
30 | public override void Draw(EffectTechnique technique) {
31 | _graphicsDevice.SetVertexBuffer(_vertexBuffer);
32 | _graphicsDevice.Indices = _indexBuffer;
33 |
34 | foreach (var pass in technique.Passes) {
35 | pass.Apply();
36 | _graphicsDevice.DrawIndexedPrimitives(PrimitiveType.TriangleList, 0, 0, 4);
37 | }
38 | }
39 |
40 | public void SetVerticesXY(Vector3 start, Vector3 end, Color color, float rightAngleSideWidth) {
41 | // Half of the width of the right angle side
42 | var std = rightAngleSideWidth / 2;
43 | var dx = std;
44 | var dzDown = std / 2;
45 | var dzUp = dzDown;
46 |
47 | var vertices = new[] {
48 | new VertexPositionColorTexture {Position = new Vector3(start.X, start.Y, start.Z + dzUp), Color = color, TextureCoordinate = new Vector2(1, 1)},
49 | new VertexPositionColorTexture {Position = new Vector3(start.X - dx, start.Y, start.Z - dzDown), Color = color, TextureCoordinate = new Vector2(0, 1)},
50 | new VertexPositionColorTexture {Position = new Vector3(start.X + dx, start.Y, start.Z - dzDown), Color = color, TextureCoordinate = new Vector2(0, 1)},
51 | new VertexPositionColorTexture {Position = new Vector3(end.X, end.Y, end.Z + dzUp), Color = color, TextureCoordinate = new Vector2(1, 0)},
52 | new VertexPositionColorTexture {Position = new Vector3(end.X - dx, end.Y, end.Z - dzDown), Color = color, TextureCoordinate = new Vector2(0, 0)},
53 | new VertexPositionColorTexture {Position = new Vector3(end.X + dx, end.Y, end.Z - dzDown), Color = color, TextureCoordinate = new Vector2(0, 0)}
54 | };
55 |
56 | _vertexBuffer.SetData(vertices);
57 | }
58 |
59 | protected override void Dispose(bool disposing) {
60 | _vertexBuffer.Dispose();
61 | _indexBuffer.Dispose();
62 |
63 | _vertexBuffer = null;
64 | _indexBuffer = null;
65 | }
66 |
67 | private readonly GraphicsDevice _graphicsDevice;
68 | private VertexBuffer _vertexBuffer;
69 | private IndexBuffer _indexBuffer;
70 |
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/ArcaeaView/Subsystems/Rendering/Camera.cs:
--------------------------------------------------------------------------------
1 | using JetBrains.Annotations;
2 | using Microsoft.Xna.Framework;
3 | using Microsoft.Xna.Framework.Graphics;
4 | using Moe.Mottomo.ArcaeaSim.Core;
5 |
6 | namespace Moe.Mottomo.ArcaeaSim.Subsystems.Rendering {
7 | ///
8 | /// A simple FPS-like perspective camera.
9 | ///
10 | public sealed class Camera {
11 |
12 | ///
13 | /// Creates a new instance.
14 | ///
15 | ///
16 | public Camera([NotNull] GraphicsDevice graphicsDevice) {
17 | _graphicsDevice = graphicsDevice;
18 |
19 | Update();
20 | }
21 |
22 | ///
23 | /// Gets/sets the position of the camera.
24 | ///
25 | public Vector3 Position { get; set; }
26 |
27 | ///
28 | /// Gets the view matrix of the camera.
29 | ///
30 | public Matrix ViewMatrix { get; private set; }
31 |
32 | ///
33 | /// Gets the projection matrix of the camera.
34 | ///
35 | public Matrix ProjectionMatrix { get; private set; }
36 |
37 | ///
38 | /// Gets/sets the target to look at.
39 | ///
40 | public Vector3 LookAtTarget {
41 | get => _lookAtTarget;
42 | set {
43 | _lookAtTarget = value;
44 | _forward = value - Position;
45 | _right = -Vector3.Normalize(Vector3.Cross(_up, _forward));
46 | }
47 | }
48 |
49 | ///
50 | /// Gets/sets the up vector.
51 | ///
52 | public Vector3 Up {
53 | get => _up;
54 | set {
55 | _up = value;
56 | _forward = _lookAtTarget - Position;
57 | _right = -Vector3.Normalize(Vector3.Cross(value, _forward));
58 | }
59 | }
60 |
61 | ///
62 | /// Gets/sets the field of view.
63 | ///
64 | public float FieldOfView {
65 | get => _fieldOfView;
66 | set => _fieldOfView = MathF.ClampLower(value, 0.01f);
67 | }
68 |
69 | ///
70 | /// Gets/sets the near clip distance.
71 | ///
72 | public float NearClip {
73 | get => _nearClip;
74 | set => _nearClip = MathF.ClampLower(value, 0.0001f);
75 | }
76 |
77 | ///
78 | /// Gets/sets the far clip distance.
79 | ///
80 | public float FarClip {
81 | get => _farClip;
82 | set => _farClip = MathF.ClampLower(value, 0.1f);
83 | }
84 |
85 | ///
86 | /// Gets the right vector.
87 | ///
88 | public Vector3 Right {
89 | get => _right;
90 | private set => _right = value;
91 | }
92 |
93 | ///
94 | /// Gets the forward vector.
95 | ///
96 | public Vector3 Forward {
97 | get => _forward;
98 | private set => _forward = value;
99 | }
100 |
101 | ///
102 | /// Strafe left or right.
103 | ///
104 | /// Strafe distance. Use a positive value to strafe right, and negative value to strafe left.
105 | public void Strafe(float rightDistance) {
106 | _right.Normalize();
107 |
108 | var delta = _right * rightDistance;
109 | Position += delta;
110 | _lookAtTarget += delta;
111 | }
112 |
113 | ///
114 | /// Walk forward or backwards.
115 | ///
116 | /// Walk distance. Use a positive value to walk forward, and negative value to walk backwards.
117 | public void Walk(float frontDistance) {
118 | _forward.Normalize();
119 |
120 | var delta = _forward * frontDistance;
121 | Position += delta;
122 | _lookAtTarget += delta;
123 | }
124 |
125 | ///
126 | /// Rotate X.
127 | ///
128 | /// The delta rotation angle, in radians. Use a positive value to rotate down, and a negative value to rotate up.
129 | public void Pitch(float amount) {
130 | _pitch += amount;
131 |
132 | _forward.Normalize();
133 |
134 | var left = Vector3.Cross(_up, _forward);
135 | left.Normalize();
136 |
137 | var length = (_lookAtTarget - Position).Length();
138 |
139 | _right = -left;
140 | _forward = Vector3.Transform(_forward, Matrix.CreateFromAxisAngle(left, amount));
141 | _up = Vector3.Transform(_up, Matrix.CreateFromAxisAngle(left, amount));
142 |
143 | _lookAtTarget = Position + _forward * length;
144 | }
145 |
146 | ///
147 | /// Rotate Z.
148 | ///
149 | /// The delta rotation angle, in radians. Use a positive value to rotate left, and a negative value to rotate right.
150 | public void Yaw(float amount) {
151 | _yaw += amount;
152 |
153 | _forward.Normalize();
154 |
155 | var length = (_lookAtTarget - Position).Length();
156 |
157 | _forward = Vector3.Transform(_forward, Matrix.CreateFromAxisAngle(_up, amount));
158 |
159 | _lookAtTarget = Position + _forward * length;
160 | }
161 |
162 | ///
163 | /// Rotate Y.
164 | ///
165 | /// The delta rotation angle, in radians. Use a positive value to rotate counterclockwise, and a negative value to rotate clockwise.
166 | public void Roll(float amount) {
167 | _roll += amount;
168 |
169 | _up.Normalize();
170 | var left = Vector3.Cross(_up, _forward);
171 | left.Normalize();
172 |
173 | _up = Vector3.Transform(_up, Matrix.CreateFromAxisAngle(_forward, amount));
174 | }
175 |
176 | ///
177 | /// Zoom in or zoom out.
178 | ///
179 | /// The delta scale. Affects FOV.
180 | public void Zoom(float deltaScale) {
181 | var newFov = MathHelper.Clamp(FieldOfView + deltaScale, 0.1f, MathHelper.Pi);
182 |
183 | FieldOfView = newFov;
184 | }
185 |
186 | ///
187 | /// Update the view matrix and the projection matrix of the camera.
188 | ///
189 | public void Update() {
190 | var view = Matrix.CreateLookAt(Position, LookAtTarget, Up);
191 |
192 | var aspectRatio = _graphicsDevice.Viewport.AspectRatio;
193 | var projection = Matrix.CreatePerspectiveFieldOfView(FieldOfView, aspectRatio, NearClip, FarClip);
194 |
195 | ViewMatrix = view;
196 | ProjectionMatrix = projection;
197 | }
198 |
199 | ///
200 | /// Reset camera parameters to initial state (not default state).
201 | ///
202 | public void Reset() {
203 | Position = Vector3.UnitZ * ViewerHeight;
204 | LookAtTarget = new Vector3(0, FarClip * 0.2f, 0);
205 | FieldOfView = MathHelper.ToRadians(60);
206 |
207 | _forward = _lookAtTarget - Position;
208 | _forward.Normalize();
209 | _right = Vector3.UnitX;
210 | _up = Vector3.Cross(_right, _forward);
211 |
212 | _pitch = 0;
213 | _yaw = 0;
214 | _roll = 0;
215 | }
216 |
217 | private const float ViewerHeight = 10.5f;
218 |
219 | private Vector3 _up = Vector3.UnitZ;
220 | private Vector3 _lookAtTarget;
221 | private Vector3 _forward = Vector3.UnitY;
222 | private Vector3 _right = Vector3.UnitX;
223 |
224 | private float _fieldOfView = MathHelper.PiOver2;
225 |
226 | private float _nearClip = 0.5f;
227 | private float _farClip = 120f;
228 |
229 | private float _pitch;
230 | private float _yaw;
231 | private float _roll;
232 |
233 | private readonly GraphicsDevice _graphicsDevice;
234 |
235 | }
236 | }
237 |
--------------------------------------------------------------------------------
/ArcaeaView/Subsystems/Rendering/ColoredBox.cs:
--------------------------------------------------------------------------------
1 | using JetBrains.Annotations;
2 | using Microsoft.Xna.Framework;
3 | using Microsoft.Xna.Framework.Graphics;
4 |
5 | namespace Moe.Mottomo.ArcaeaSim.Subsystems.Rendering {
6 | ///
7 | ///
8 | /// Represents a colored box. A box is defined by its bottom left back corner (near to the X axis) and its size on X, Y and Z axes. Its faces are parallel to the axes.
9 | /// Uses vertex format .
10 | ///
11 | public sealed class ColoredBox : DrawableGeometryMesh {
12 |
13 | ///
14 | /// Creates a new instance.
15 | ///
16 | /// The to use for rendering the box.
17 | public ColoredBox([NotNull] GraphicsDevice graphicsDevice) {
18 | _graphicsDevice = graphicsDevice;
19 |
20 | _vertexBuffer = new VertexBuffer(graphicsDevice, VertexPositionColor.VertexDeclaration, 8, BufferUsage.WriteOnly);
21 | _indexBuffer = new IndexBuffer(graphicsDevice, IndexElementSize.SixteenBits, 36, BufferUsage.WriteOnly);
22 |
23 | var indices = new ushort[] {
24 | 0, 1, 2,
25 | 2, 1, 3,
26 | 4, 5, 6,
27 | 6, 5, 7,
28 |
29 | 0, 1, 4,
30 | 1, 4, 5,
31 | 2, 3, 6,
32 | 3, 6, 7,
33 | 0, 2, 4,
34 | 2, 4, 6,
35 | 1, 3, 5,
36 | 3, 5, 7
37 | };
38 |
39 | _indexBuffer.SetData(indices);
40 | }
41 |
42 | public override void Draw(EffectTechnique technique) {
43 | _graphicsDevice.SetVertexBuffer(_vertexBuffer);
44 | _graphicsDevice.Indices = _indexBuffer;
45 |
46 | foreach (var pass in technique.Passes) {
47 | pass.Apply();
48 | _graphicsDevice.DrawIndexedPrimitives(PrimitiveType.TriangleList, 0, 0, 12);
49 | }
50 | }
51 |
52 | ///
53 | /// Set and update mesh vertices.
54 | ///
55 | /// The bottom left back point.
56 | /// Size of this box.
57 | /// The color of this box.
58 | public void SetVertices(Vector3 bottomNearLeft, Vector3 size, Color color) {
59 | var vertices = new[] {
60 | new VertexPositionColor {Position = new Vector3(bottomNearLeft.X, bottomNearLeft.Y, bottomNearLeft.Z), Color = color},
61 | new VertexPositionColor {Position = new Vector3(bottomNearLeft.X + size.X, bottomNearLeft.Y, bottomNearLeft.Z), Color = color},
62 | new VertexPositionColor {Position = new Vector3(bottomNearLeft.X, bottomNearLeft.Y + size.Y, bottomNearLeft.Z), Color = color},
63 | new VertexPositionColor {Position = new Vector3(bottomNearLeft.X + size.X, bottomNearLeft.Y + size.Y, bottomNearLeft.Z), Color = color},
64 | new VertexPositionColor {Position = new Vector3(bottomNearLeft.X, bottomNearLeft.Y, bottomNearLeft.Z + size.Z), Color = color},
65 | new VertexPositionColor {Position = new Vector3(bottomNearLeft.X + size.X, bottomNearLeft.Y, bottomNearLeft.Z + size.Z), Color = color},
66 | new VertexPositionColor {Position = new Vector3(bottomNearLeft.X, bottomNearLeft.Y + size.Y, bottomNearLeft.Z + size.Z), Color = color},
67 | new VertexPositionColor {Position = new Vector3(bottomNearLeft.X + size.X, bottomNearLeft.Y + size.Y, bottomNearLeft.Z + size.Z), Color = color}
68 | };
69 |
70 | _vertexBuffer.SetData(vertices);
71 | }
72 |
73 | protected override void Dispose(bool disposing) {
74 | _vertexBuffer.Dispose();
75 | _indexBuffer.Dispose();
76 |
77 | _vertexBuffer = null;
78 | _indexBuffer = null;
79 | }
80 |
81 | private readonly GraphicsDevice _graphicsDevice;
82 | private VertexBuffer _vertexBuffer;
83 | private IndexBuffer _indexBuffer;
84 |
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/ArcaeaView/Subsystems/Rendering/ColoredHexahedron.cs:
--------------------------------------------------------------------------------
1 | using JetBrains.Annotations;
2 | using Microsoft.Xna.Framework;
3 | using Microsoft.Xna.Framework.Graphics;
4 |
5 | namespace Moe.Mottomo.ArcaeaSim.Subsystems.Rendering {
6 | ///
7 | ///
8 | /// Uses .
9 | ///
10 | public sealed class ColoredHexahedron : DrawableGeometryMesh {
11 |
12 | public ColoredHexahedron([NotNull] GraphicsDevice graphicsDevice) {
13 | _graphicsDevice = graphicsDevice;
14 |
15 | _vertexBuffer = new VertexBuffer(graphicsDevice, VertexPositionColor.VertexDeclaration, 8, BufferUsage.WriteOnly);
16 | _indexBuffer = new IndexBuffer(graphicsDevice, IndexElementSize.SixteenBits, 36, BufferUsage.WriteOnly);
17 |
18 | var indices = new ushort[] {
19 | 0, 1, 2,
20 | 2, 1, 3,
21 | 4, 5, 6,
22 | 6, 5, 7,
23 |
24 | 0, 1, 4,
25 | 1, 4, 5,
26 | 2, 3, 6,
27 | 3, 6, 7,
28 | 0, 2, 4,
29 | 2, 4, 6,
30 | 1, 3, 5,
31 | 3, 5, 7
32 | };
33 |
34 | _indexBuffer.SetData(indices);
35 | }
36 |
37 | public override void Draw(EffectTechnique technique) {
38 | _graphicsDevice.SetVertexBuffer(_vertexBuffer);
39 | _graphicsDevice.Indices = _indexBuffer;
40 |
41 | foreach (var pass in technique.Passes) {
42 | pass.Apply();
43 | _graphicsDevice.DrawIndexedPrimitives(PrimitiveType.TriangleList, 0, 0, 12);
44 | }
45 | }
46 |
47 | public void SetVerticesXY(Vector3 start, Vector3 end, Color color, Vector2 sectionSize) {
48 | var vertices = new[] {
49 | new VertexPositionColor {Position = new Vector3(start.X - sectionSize.X / 2, start.Y, start.Z - sectionSize.Y / 2), Color = color},
50 | new VertexPositionColor {Position = new Vector3(start.X + sectionSize.X / 2, start.Y, start.Z - sectionSize.Y / 2), Color = color},
51 | new VertexPositionColor {Position = new Vector3(start.X - sectionSize.X / 2, start.Y, start.Z + sectionSize.Y / 2), Color = color},
52 | new VertexPositionColor {Position = new Vector3(start.X + sectionSize.X / 2, start.Y, start.Z + sectionSize.Y / 2), Color = color},
53 | new VertexPositionColor {Position = new Vector3(end.X - sectionSize.X / 2, end.Y, end.Z - sectionSize.Y / 2), Color = color},
54 | new VertexPositionColor {Position = new Vector3(end.X + sectionSize.X / 2, end.Y, end.Z - sectionSize.Y / 2), Color = color},
55 | new VertexPositionColor {Position = new Vector3(end.X - sectionSize.X / 2, end.Y, end.Z + sectionSize.Y / 2), Color = color},
56 | new VertexPositionColor {Position = new Vector3(end.X + sectionSize.X / 2, end.Y, end.Z + sectionSize.Y / 2), Color = color}
57 | };
58 |
59 | _vertexBuffer.SetData(vertices);
60 | }
61 |
62 | public void SetVerticesXZ(Vector3 start, Vector3 end, Color color, Vector2 sectionSize) {
63 | var vertices = new[] {
64 | new VertexPositionColor {Position = new Vector3(start.X - sectionSize.X / 2, start.Y - sectionSize.Y / 2, start.Z), Color = color},
65 | new VertexPositionColor {Position = new Vector3(start.X + sectionSize.X / 2, start.Y - sectionSize.Y / 2, start.Z), Color = color},
66 | new VertexPositionColor {Position = new Vector3(start.X - sectionSize.X / 2, start.Y + sectionSize.Y / 2, start.Z), Color = color},
67 | new VertexPositionColor {Position = new Vector3(start.X + sectionSize.X / 2, start.Y + sectionSize.Y / 2, start.Z), Color = color},
68 | new VertexPositionColor {Position = new Vector3(end.X - sectionSize.X / 2, end.Y - sectionSize.Y / 2, end.Z), Color = color},
69 | new VertexPositionColor {Position = new Vector3(end.X + sectionSize.X / 2, end.Y - sectionSize.Y / 2, end.Z), Color = color},
70 | new VertexPositionColor {Position = new Vector3(end.X - sectionSize.X / 2, end.Y + sectionSize.Y / 2, end.Z), Color = color},
71 | new VertexPositionColor {Position = new Vector3(end.X + sectionSize.X / 2, end.Y + sectionSize.Y / 2, end.Z), Color = color}
72 | };
73 |
74 | _vertexBuffer.SetData(vertices);
75 | }
76 |
77 | protected override void Dispose(bool disposing) {
78 | _vertexBuffer.Dispose();
79 | _indexBuffer.Dispose();
80 |
81 | _vertexBuffer = null;
82 | _indexBuffer = null;
83 | }
84 |
85 | private readonly GraphicsDevice _graphicsDevice;
86 | private VertexBuffer _vertexBuffer;
87 | private IndexBuffer _indexBuffer;
88 |
89 | }
90 | }
91 |
--------------------------------------------------------------------------------
/ArcaeaView/Subsystems/Rendering/ColoredParallelogram.cs:
--------------------------------------------------------------------------------
1 | using JetBrains.Annotations;
2 | using Microsoft.Xna.Framework;
3 | using Microsoft.Xna.Framework.Graphics;
4 | using Moe.Mottomo.ArcaeaSim.Core;
5 |
6 | namespace Moe.Mottomo.ArcaeaSim.Subsystems.Rendering {
7 | ///
8 | ///
9 | /// Uses .
10 | ///
11 | public sealed class ColoredParallelogram : DrawableGeometryMesh {
12 |
13 | public ColoredParallelogram([NotNull] GraphicsDevice graphicsDevice) {
14 | _graphicsDevice = graphicsDevice;
15 |
16 | _vertexBuffer = new VertexBuffer(graphicsDevice, VertexPositionColor.VertexDeclaration, 4, BufferUsage.WriteOnly);
17 | _indexBuffer = new IndexBuffer(graphicsDevice, IndexElementSize.SixteenBits, 6, BufferUsage.WriteOnly);
18 |
19 | var indices = new ushort[] {
20 | 0, 1, 2,
21 | 2, 1, 3
22 | };
23 |
24 | _indexBuffer.SetData(indices);
25 | }
26 |
27 | public override void Draw(EffectTechnique technique) {
28 | _graphicsDevice.SetVertexBuffer(_vertexBuffer);
29 | _graphicsDevice.Indices = _indexBuffer;
30 |
31 | foreach (var pass in technique.Passes) {
32 | pass.Apply();
33 | _graphicsDevice.DrawIndexedPrimitives(PrimitiveType.TriangleList, 0, 0, 2);
34 | }
35 | }
36 |
37 | public void SetVerticesXY(Vector2 start, Vector2 end, float width, Color color, float z = 0) {
38 | var angle = MathF.Atan2(end.Y - start.Y, end.X - start.X);
39 | var dx = width / 2 * MathF.Sin(angle);
40 | var dy = width / 2 * MathF.Cos(angle);
41 |
42 | var vertices = new[] {
43 | new VertexPositionColor {Position = new Vector3(start.X - dx, start.Y - dy, z), Color = color},
44 | new VertexPositionColor {Position = new Vector3(start.X + dx, start.Y + dy, z), Color = color},
45 | new VertexPositionColor {Position = new Vector3(end.X - dx, end.Y - dy, z), Color = color},
46 | new VertexPositionColor {Position = new Vector3(end.X + dx, end.Y + dy, z), Color = color}
47 | };
48 |
49 | _vertexBuffer.SetData(vertices);
50 | }
51 |
52 | public void SetVerticesXYParallel(Vector2 start, Vector2 end, float width, Color color, float z = 0) {
53 | var halfWidth = width / 2;
54 |
55 | var vertices = new[] {
56 | new VertexPositionColor {Position = new Vector3(start.X - halfWidth, start.Y, z), Color = color},
57 | new VertexPositionColor {Position = new Vector3(start.X + halfWidth, start.Y, z), Color = color},
58 | new VertexPositionColor {Position = new Vector3(end.X - halfWidth, end.Y, z), Color = color},
59 | new VertexPositionColor {Position = new Vector3(end.X + halfWidth, end.Y, z), Color = color}
60 | };
61 |
62 | _vertexBuffer.SetData(vertices);
63 | }
64 |
65 | protected override void Dispose(bool disposing) {
66 | _vertexBuffer.Dispose();
67 | _indexBuffer.Dispose();
68 |
69 | _vertexBuffer = null;
70 | _indexBuffer = null;
71 | }
72 |
73 | private readonly GraphicsDevice _graphicsDevice;
74 | private VertexBuffer _vertexBuffer;
75 | private IndexBuffer _indexBuffer;
76 |
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/ArcaeaView/Subsystems/Rendering/ColoredRectangle.cs:
--------------------------------------------------------------------------------
1 | using JetBrains.Annotations;
2 | using Microsoft.Xna.Framework;
3 | using Microsoft.Xna.Framework.Graphics;
4 |
5 | namespace Moe.Mottomo.ArcaeaSim.Subsystems.Rendering {
6 | ///
7 | ///
8 | /// Uses .
9 | ///
10 | public sealed class ColoredRectangle : DrawableGeometryMesh {
11 |
12 | public ColoredRectangle([NotNull] GraphicsDevice graphicsDevice) {
13 | _graphicsDevice = graphicsDevice;
14 |
15 | _vertexBuffer = new VertexBuffer(graphicsDevice, VertexPositionColor.VertexDeclaration, 4, BufferUsage.WriteOnly);
16 | _indexBuffer = new IndexBuffer(graphicsDevice, IndexElementSize.SixteenBits, 6, BufferUsage.WriteOnly);
17 |
18 | var indices = new ushort[] {
19 | 0, 1, 2,
20 | 2, 1, 3
21 | };
22 |
23 | _indexBuffer.SetData(indices);
24 | }
25 |
26 | public override void Draw(EffectTechnique technique) {
27 | _graphicsDevice.SetVertexBuffer(_vertexBuffer);
28 | _graphicsDevice.Indices = _indexBuffer;
29 |
30 | foreach (var pass in technique.Passes) {
31 | pass.Apply();
32 | _graphicsDevice.DrawIndexedPrimitives(PrimitiveType.TriangleList, 0, 0, 2);
33 | }
34 | }
35 |
36 | public void SetVerticesXY(Vector2 bottomLeft, Vector2 size, Color color, float z = 0) {
37 | var vertices = new[] {
38 | new VertexPositionColor {Position = new Vector3(bottomLeft.X, bottomLeft.Y, z), Color = color},
39 | new VertexPositionColor {Position = new Vector3(bottomLeft.X + size.X, bottomLeft.Y, z), Color = color},
40 | new VertexPositionColor {Position = new Vector3(bottomLeft.X, bottomLeft.Y + size.Y, z), Color = color},
41 | new VertexPositionColor {Position = new Vector3(bottomLeft.X + size.X, bottomLeft.Y + size.Y, z), Color = color}
42 | };
43 |
44 | _vertexBuffer.SetData(vertices);
45 | }
46 |
47 | protected override void Dispose(bool disposing) {
48 | _vertexBuffer.Dispose();
49 | _indexBuffer.Dispose();
50 |
51 | _vertexBuffer = null;
52 | _indexBuffer = null;
53 | }
54 |
55 | private readonly GraphicsDevice _graphicsDevice;
56 | private VertexBuffer _vertexBuffer;
57 | private IndexBuffer _indexBuffer;
58 |
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/ArcaeaView/Subsystems/Rendering/DrawableGeometryMesh.cs:
--------------------------------------------------------------------------------
1 | using JetBrains.Annotations;
2 | using Microsoft.Xna.Framework.Graphics;
3 | using OpenMLTD.MilliSim.Core;
4 |
5 | namespace Moe.Mottomo.ArcaeaSim.Subsystems.Rendering {
6 | ///
7 | ///
8 | /// Represents a drawable mesh of a simple geometry.
9 | ///
10 | public abstract class DrawableGeometryMesh : DisposableBase {
11 |
12 | ///
13 | /// Draws this mesh. Be sure to have its vertex buffer and index buffer ready before calling.
14 | ///
15 | /// The to use to draw this mesh.
16 | public abstract void Draw([NotNull] EffectTechnique technique);
17 |
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/ArcaeaView/Subsystems/Rendering/TexturedRectangle.cs:
--------------------------------------------------------------------------------
1 | using JetBrains.Annotations;
2 | using Microsoft.Xna.Framework;
3 | using Microsoft.Xna.Framework.Graphics;
4 |
5 | namespace Moe.Mottomo.ArcaeaSim.Subsystems.Rendering {
6 | ///
7 | ///
8 | /// Uses .
9 | ///
10 | public sealed class TexturedRectangle : DrawableGeometryMesh {
11 |
12 | public TexturedRectangle([NotNull] GraphicsDevice graphicsDevice) {
13 | _graphicsDevice = graphicsDevice;
14 |
15 | _vertexBuffer = new VertexBuffer(graphicsDevice, VertexPositionColorTexture.VertexDeclaration, 4, BufferUsage.WriteOnly);
16 | _indexBuffer = new IndexBuffer(graphicsDevice, IndexElementSize.SixteenBits, 6, BufferUsage.WriteOnly);
17 |
18 | var indices = new ushort[] {
19 | 0, 1, 2,
20 | 2, 1, 3
21 | };
22 |
23 | _indexBuffer.SetData(indices);
24 | }
25 |
26 | public override void Draw(EffectTechnique technique) {
27 | _graphicsDevice.SetVertexBuffer(_vertexBuffer);
28 | _graphicsDevice.Indices = _indexBuffer;
29 |
30 | foreach (var pass in technique.Passes) {
31 | pass.Apply();
32 | _graphicsDevice.DrawIndexedPrimitives(PrimitiveType.TriangleList, 0, 0, 2);
33 | }
34 | }
35 |
36 | public void SetVerticesXY(Vector2 origin, Vector2 size, Color color, float z) {
37 | var vertices = new[] {
38 | new VertexPositionColorTexture {Position = new Vector3(origin.X, origin.Y, z), Color = color, TextureCoordinate = new Vector2(0, 1)},
39 | new VertexPositionColorTexture {Position = new Vector3(origin.X + size.X, origin.Y, z), Color = color, TextureCoordinate = new Vector2(1, 1)},
40 | new VertexPositionColorTexture {Position = new Vector3(origin.X, origin.Y + size.Y, z), Color = color, TextureCoordinate = new Vector2(0, 0)},
41 | new VertexPositionColorTexture {Position = new Vector3(origin.X + size.X, origin.Y + size.Y, z), Color = color, TextureCoordinate = new Vector2(1, 0)}
42 | };
43 |
44 | _vertexBuffer.SetData(vertices);
45 | }
46 |
47 | public void SetVerticesXZ(Vector2 origin, Vector2 size, Color color, float y) {
48 | var vertices = new[] {
49 | new VertexPositionColorTexture {Position = new Vector3(origin.X, y, origin.Y), Color = color, TextureCoordinate = new Vector2(0, 1)},
50 | new VertexPositionColorTexture {Position = new Vector3(origin.X + size.X, y, origin.Y), Color = color, TextureCoordinate = new Vector2(1, 1)},
51 | new VertexPositionColorTexture {Position = new Vector3(origin.X, y, origin.Y + size.Y), Color = color, TextureCoordinate = new Vector2(0, 0)},
52 | new VertexPositionColorTexture {Position = new Vector3(origin.X + size.X, y, origin.Y + size.Y), Color = color, TextureCoordinate = new Vector2(1, 0)}
53 | };
54 |
55 | _vertexBuffer.SetData(vertices);
56 | }
57 |
58 | public void SetVerticesXY(Vector2 origin, Vector2 size, Color color, float textureYOffset, float z) {
59 | var y = size.Y + textureYOffset;
60 |
61 | var vertices = new[] {
62 | new VertexPositionColorTexture {Position = new Vector3(origin.X, origin.Y, z), Color = color, TextureCoordinate = new Vector2(0, y)},
63 | new VertexPositionColorTexture {Position = new Vector3(origin.X + size.X, origin.Y, z), Color = color, TextureCoordinate = new Vector2(1, y)},
64 | new VertexPositionColorTexture {Position = new Vector3(origin.X, origin.Y + size.Y, z), Color = color, TextureCoordinate = new Vector2(0, textureYOffset)},
65 | new VertexPositionColorTexture {Position = new Vector3(origin.X + size.X, origin.Y + size.Y, z), Color = color, TextureCoordinate = new Vector2(1, textureYOffset)}
66 | };
67 |
68 | _vertexBuffer.SetData(vertices);
69 | }
70 |
71 | // Rotated, counterclockwise, 90deg
72 | public void SetVerticesXZTextureRotated(Vector2 origin, Vector2 size, Color color, float y) {
73 | var vertices = new[] {
74 | new VertexPositionColorTexture {Position = new Vector3(origin.X, y, origin.Y), Color = color, TextureCoordinate = new Vector2(0, 0)},
75 | new VertexPositionColorTexture {Position = new Vector3(origin.X + size.X, y, origin.Y), Color = color, TextureCoordinate = new Vector2(0, 1)},
76 | new VertexPositionColorTexture {Position = new Vector3(origin.X, y, origin.Y + size.Y), Color = color, TextureCoordinate = new Vector2(1, 0)},
77 | new VertexPositionColorTexture {Position = new Vector3(origin.X + size.X, y, origin.Y + size.Y), Color = color, TextureCoordinate = new Vector2(1, 1)}
78 | };
79 |
80 | _vertexBuffer.SetData(vertices);
81 | }
82 |
83 | protected override void Dispose(bool disposing) {
84 | _vertexBuffer.Dispose();
85 | _indexBuffer.Dispose();
86 |
87 | _vertexBuffer = null;
88 | _indexBuffer = null;
89 | }
90 |
91 | private readonly GraphicsDevice _graphicsDevice;
92 | private VertexBuffer _vertexBuffer;
93 | private IndexBuffer _indexBuffer;
94 |
95 | }
96 | }
97 |
--------------------------------------------------------------------------------
/ArcaeaView/Subsystems/Scores/Entities/ArcColor.cs:
--------------------------------------------------------------------------------
1 | namespace Moe.Mottomo.ArcaeaSim.Subsystems.Scores.Entities {
2 | ///
3 | /// Color of an arc, if it is a playable arc (i.e. must be tracked with your fingers).
4 | ///
5 | public enum ArcColor {
6 |
7 | ///
8 | /// Light blue.
9 | ///
10 | LightBlue = 0,
11 | ///
12 | /// Magenta.
13 | ///
14 | Magenta = 1
15 |
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/ArcaeaView/Subsystems/Scores/Entities/ArcEasing.cs:
--------------------------------------------------------------------------------
1 | namespace Moe.Mottomo.ArcaeaSim.Subsystems.Scores.Entities {
2 | ///
3 | /// Arc easing method.
4 | ///
5 | public enum ArcEasing {
6 |
7 | ///
8 | /// Linear.
9 | ///
10 | S = 0,
11 |
12 | ///
13 | /// Cubic bezier, with control points at 1/4 and 3/4.
14 | ///
15 | CubicBezier = 1,
16 |
17 | ///
18 | /// Ease in as a quarter circle, then linear.
19 | ///
20 | Si = 2,
21 | ///
22 | /// Linear, then ease out as a quarter circle.
23 | ///
24 | So = 3,
25 |
26 | ///
27 | /// Ease in as a quarter circle.
28 | ///
29 | SiSo = 4,
30 | ///
31 | /// Ease out as a quarter circle.
32 | ///
33 | SoSi = 5,
34 |
35 | ///
36 | /// Behavior unknown.
37 | ///
38 | SiSi = 6,
39 | ///
40 | /// Behavior unknown.
41 | ///
42 | SoSo = 7
43 |
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/ArcaeaView/Subsystems/Scores/Entities/ArcNote.cs:
--------------------------------------------------------------------------------
1 | using JetBrains.Annotations;
2 |
3 | namespace Moe.Mottomo.ArcaeaSim.Subsystems.Scores.Entities {
4 | ///
5 | ///
6 | ///
7 | /// Represents an arc note.
8 | ///
9 | public sealed class ArcNote : NoteBase, IHasTicks {
10 |
11 | public int StartTick { get; set; }
12 |
13 | public int EndTick { get; set; }
14 |
15 | ///
16 | /// Start X position. The playable region should be [-0.5, 1.5].
17 | ///
18 | public float StartX { get; set; }
19 |
20 | ///
21 | /// End X position. The playable region should be [-0.5, 1.5].
22 | ///
23 | public float EndX { get; set; }
24 |
25 | ///
26 | /// Easing method.
27 | ///
28 | public ArcEasing Easing { get; set; }
29 |
30 | ///
31 | /// Start Y position. The playable region should be [0, 1].
32 | ///
33 | public float StartY { get; set; }
34 |
35 | ///
36 | /// End Y position. The playable region should be [0, 1].
37 | ///
38 | public float EndY { get; set; }
39 |
40 | ///
41 | /// Color of the arc. Only matters if is .
42 | ///
43 | public ArcColor Color { get; set; }
44 |
45 | ///
46 | /// Unknown field. The most common value is "none"
.
47 | ///
48 | public string Unknown1 { get; set; }
49 |
50 | ///
51 | /// Whether this note a trace arc (i.e. not playble, translucent).
52 | ///
53 | public bool IsTraceArc { get; set; }
54 |
55 | ///
56 | /// Whether this note a playable arc (i.e. must be tracked with your fingers).
57 | ///
58 | public bool IsPlayable => !IsTraceArc;
59 |
60 | ///
61 | /// The sky notes associated with this note.
62 | /// The array has a value (i.e. is not null) only when is .
63 | ///
64 | [CanBeNull]
65 | public SkyNote[] SkyNotes { get; set; }
66 |
67 | public override NoteType Type => NoteType.Arc;
68 |
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/ArcaeaView/Subsystems/Scores/Entities/Beatmap.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Diagnostics;
4 | using System.IO;
5 | using System.Runtime.CompilerServices;
6 | using System.Text;
7 | using System.Text.RegularExpressions;
8 | using JetBrains.Annotations;
9 |
10 | namespace Moe.Mottomo.ArcaeaSim.Subsystems.Scores.Entities {
11 | ///
12 | /// Represents an Arcaea beatmap.
13 | ///
14 | public sealed class Beatmap {
15 |
16 | ///
17 | /// Audio offset, in milliseconds.
18 | ///
19 | public int AudioOffset { get; set; }
20 |
21 | ///
22 | /// Notes in the beatmap.
23 | ///
24 | [NotNull]
25 | public NoteBase[] Notes { get; set; } = new NoteBase[0];
26 |
27 | public static bool TryParse([NotNull] StreamReader reader, [CanBeNull] out Beatmap beatmap) {
28 | try {
29 | beatmap = Parse(reader);
30 | return true;
31 | } catch (Exception) {
32 | beatmap = null;
33 | return false;
34 | }
35 | }
36 |
37 | public static bool TryParse([NotNull] string text, [CanBeNull] out Beatmap beatmap) {
38 | try {
39 | beatmap = Parse(text);
40 | return true;
41 | } catch (Exception) {
42 | beatmap = null;
43 | return false;
44 | }
45 | }
46 |
47 | [NotNull]
48 | public static Beatmap Parse([NotNull] string text) {
49 | var bytes = Encoding.UTF8.GetBytes(text);
50 |
51 | using (var memoryStream = new MemoryStream(bytes, false)) {
52 | using (var reader = new StreamReader(memoryStream, Encoding.UTF8)) {
53 | return Parse(reader);
54 | }
55 | }
56 | }
57 |
58 | [NotNull]
59 | public static Beatmap Parse([NotNull] StreamReader reader) {
60 | var line = reader.ReadLine();
61 | var lineCounter = 1;
62 |
63 | Debug.Assert(line != null, nameof(line) + " != null");
64 | Debug.Assert(line.StartsWith("AudioOffset:"));
65 |
66 | var audioOffset = int.Parse(line.Substring("AudioOffset:".Length));
67 |
68 | reader.ReadLine(); // "-"
69 | ++lineCounter;
70 |
71 | var notes = new List();
72 |
73 | while (!reader.EndOfStream) {
74 | line = reader.ReadLine();
75 | ++lineCounter;
76 |
77 | if (string.IsNullOrWhiteSpace(line)) {
78 | continue;
79 | }
80 |
81 | if (line.StartsWith("hold")) {
82 | var match = HoldRegex.Match(line);
83 |
84 | if (!match.Success) {
85 | throw new FormatException($"Hold format error at line {lineCounter}.");
86 | }
87 |
88 | var content = match.Groups["content"].Value;
89 | var segs = content.Split(ContentSeparator);
90 |
91 | var note = new LongNote();
92 | note.StartTick = Convert.ToInt32(segs[0]);
93 | note.EndTick = Convert.ToInt32(segs[1]);
94 | note.Track = (Track)Convert.ToInt32(segs[2]);
95 |
96 | notes.Add(note);
97 | } else if (line.StartsWith("arc")) {
98 | var match = ArcRegex.Match(line);
99 |
100 | if (!match.Success) {
101 | throw new FormatException($"Arc format error at line {lineCounter}.");
102 | }
103 |
104 | var extra = match.Groups["extra"].Value;
105 | SkyNote[] arcTapNotes;
106 |
107 | if (!string.IsNullOrWhiteSpace(extra)) {
108 | var subNoteStrings = extra.Split(ContentSeparator);
109 | var arcTapNoteList = new List();
110 |
111 | foreach (var subNoteString in subNoteStrings) {
112 | var subMatch = ArcTapRegex.Match(subNoteString);
113 |
114 | if (!subMatch.Success) {
115 | throw new FormatException($"ArcTap format error at line {lineCounter}.");
116 | }
117 |
118 | var arcTapNote = new SkyNote();
119 | arcTapNote.Tick = Convert.ToInt32(subMatch.Groups["content"].Value);
120 |
121 | arcTapNoteList.Add(arcTapNote);
122 | }
123 |
124 | arcTapNotes = arcTapNoteList.ToArray();
125 | } else {
126 | arcTapNotes = null;
127 | }
128 |
129 | var content = match.Groups["content"].Value;
130 | var segs = content.Split(ContentSeparator);
131 |
132 | var note = new ArcNote();
133 | note.StartTick = Convert.ToInt32(segs[0]);
134 | note.EndTick = Convert.ToInt32(segs[1]);
135 | note.StartX = Convert.ToSingle(segs[2]);
136 | note.EndX = Convert.ToSingle(segs[3]);
137 | note.Easing = ParseEasing(segs[4]);
138 | note.StartY = Convert.ToSingle(segs[5]);
139 | note.EndY = Convert.ToSingle(segs[6]);
140 | note.Color = (ArcColor)Convert.ToInt32(segs[7]);
141 | note.Unknown1 = segs[8];
142 | note.IsTraceArc = Convert.ToBoolean(segs[9]);
143 | note.SkyNotes = arcTapNotes;
144 |
145 | notes.Add(note);
146 | } else if (line.StartsWith("timing")) {
147 | var match = TimingRegex.Match(line);
148 |
149 | if (!match.Success) {
150 | throw new FormatException($"Timing format error at line {lineCounter}.");
151 | }
152 |
153 | var content = match.Groups["content"].Value;
154 | var segs = content.Split(ContentSeparator);
155 |
156 | var note = new TimingNote();
157 | note.Tick = Convert.ToInt32(segs[0]);
158 | note.Bpm = Convert.ToSingle(segs[1]);
159 | note.BeatsPerMeasure = Convert.ToSingle(segs[2]);
160 |
161 | notes.Add(note);
162 | } else {
163 | var match = TapRegex.Match(line);
164 |
165 | if (!match.Success) {
166 | throw new FormatException($"Tap format error at line {lineCounter}.");
167 | }
168 |
169 | var content = match.Groups["content"].Value;
170 | var segs = content.Split(ContentSeparator);
171 |
172 | var note = new FloorNote();
173 | note.Tick = Convert.ToInt32(segs[0]);
174 | note.Track = (Track)Convert.ToInt32(segs[1]);
175 |
176 | notes.Add(note);
177 | }
178 | }
179 |
180 | notes.Sort(NoteBase.Compare);
181 |
182 | var beatmap = new Beatmap();
183 |
184 | beatmap.AudioOffset = audioOffset;
185 | beatmap.Notes = notes.ToArray();
186 |
187 | return beatmap;
188 | }
189 |
190 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
191 | private static ArcEasing ParseEasing([NotNull] string str) {
192 | if (str == null) {
193 | throw new ArgumentNullException(nameof(str));
194 | }
195 |
196 | var s = str.Trim().ToLowerInvariant();
197 |
198 | switch (s) {
199 | case "s":
200 | return ArcEasing.S;
201 | case "b":
202 | return ArcEasing.CubicBezier;
203 | case "si":
204 | return ArcEasing.Si;
205 | case "so":
206 | return ArcEasing.So;
207 | case "siso":
208 | return ArcEasing.SiSo;
209 | case "sosi":
210 | return ArcEasing.SoSi;
211 | case "sisi":
212 | return ArcEasing.SiSi;
213 | case "soso":
214 | return ArcEasing.SoSo;
215 | default:
216 | throw new ArgumentOutOfRangeException(nameof(str), str, $"Unknown easing type \"{str}\".");
217 | }
218 | }
219 |
220 | private static readonly Regex TapRegex = new Regex(@"^\((?[^)]+)\);$");
221 | private static readonly Regex HoldRegex = new Regex(@"^hold\((?[^)]+)\);$");
222 | private static readonly Regex ArcRegex = new Regex(@"^arc\((?[^)]+)\)(\[(?[^\]]*)\])?;$");
223 | private static readonly Regex ArcTapRegex = new Regex(@"^arctap\((?\d+)\)$");
224 | private static readonly Regex TimingRegex = new Regex(@"^timing\((?[^)]+)\);$");
225 |
226 | private static readonly char[] ContentSeparator = { ',' };
227 |
228 | }
229 | }
230 |
--------------------------------------------------------------------------------
/ArcaeaView/Subsystems/Scores/Entities/FloorNote.cs:
--------------------------------------------------------------------------------
1 | namespace Moe.Mottomo.ArcaeaSim.Subsystems.Scores.Entities {
2 | ///
3 | ///
4 | ///
5 | /// Represents a floor note.
6 | ///
7 | public sealed class FloorNote : NoteBase, IHasTick {
8 |
9 | public int Tick { get; set; }
10 |
11 | ///
12 | /// The track that this note is on.
13 | ///
14 | public Track Track { get; set; }
15 |
16 | public override NoteType Type => NoteType.Floor;
17 |
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/ArcaeaView/Subsystems/Scores/Entities/LongNote.cs:
--------------------------------------------------------------------------------
1 | namespace Moe.Mottomo.ArcaeaSim.Subsystems.Scores.Entities {
2 | ///
3 | ///
4 | ///
5 | /// Represents a long note.
6 | ///
7 | public sealed class LongNote : NoteBase, IHasTicks {
8 |
9 | public int StartTick { get; set; }
10 |
11 | public int EndTick { get; set; }
12 |
13 | ///
14 | /// The track that this note is on.
15 | ///
16 | public Track Track { get; set; }
17 |
18 | public override NoteType Type => NoteType.Long;
19 |
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/ArcaeaView/Subsystems/Scores/Entities/NoteBase.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using JetBrains.Annotations;
3 |
4 | namespace Moe.Mottomo.ArcaeaSim.Subsystems.Scores.Entities {
5 | ///
6 | /// Arcaea note base.
7 | ///
8 | public abstract class NoteBase {
9 |
10 | ///
11 | /// Type of the note.
12 | ///
13 | public abstract NoteType Type { get; }
14 |
15 | ///
16 | /// Common note comparison function. It compares the notes' tick (or start tick).
17 | ///
18 | /// The first note to be compared.
19 | /// The second note to be compared.
20 | /// Comparison result. 1, 0, -1 mean greater than, equal, and less than respectively.
21 | public static int Compare([CanBeNull] NoteBase note1, [CanBeNull] NoteBase note2) {
22 | if (note1 == null) {
23 | if (note2 == null) {
24 | return 0;
25 | } else {
26 | return -1;
27 | }
28 | } else {
29 | if (note2 == null) {
30 | return 1;
31 | } else {
32 | int tick1, tick2;
33 |
34 | if (note1 is IHasTick t1) {
35 | tick1 = t1.Tick;
36 | } else if (note1 is IHasTicks ts1) {
37 | tick1 = ts1.StartTick;
38 | } else {
39 | throw new NotSupportedException();
40 | }
41 |
42 | if (note2 is IHasTick t2) {
43 | tick2 = t2.Tick;
44 | } else if (note2 is IHasTicks ts2) {
45 | tick2 = ts2.StartTick;
46 | } else {
47 | throw new NotSupportedException();
48 | }
49 |
50 | return tick1.CompareTo(tick2);
51 | }
52 | }
53 | }
54 |
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/ArcaeaView/Subsystems/Scores/Entities/NoteType.cs:
--------------------------------------------------------------------------------
1 | namespace Moe.Mottomo.ArcaeaSim.Subsystems.Scores.Entities {
2 | ///
3 | /// Note types.
4 | ///
5 | public enum NoteType {
6 |
7 | ///
8 | /// A floor note. It appears at the bottom of the game area. It should be tapped.
9 | ///
10 | Floor = 0,
11 | ///
12 | /// A long note. It appears at the bottom of the game area. It should be pressed on start tick and released on end tick.
13 | ///
14 | Long = 1,
15 | ///
16 | /// An arc note. The complicated and special note type of Arcaea.
17 | ///
18 | Arc = 2,
19 | ///
20 | /// A timing note. It changes the tempo (BPM) information.
21 | ///
22 | Timing = 3,
23 | ///
24 | /// A sky note. It does not appear in the beatmap. This value is only for convenience of development.
25 | ///
26 | Sky = 4,
27 | Max = 4
28 |
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/ArcaeaView/Subsystems/Scores/Entities/SkyNote.cs:
--------------------------------------------------------------------------------
1 | namespace Moe.Mottomo.ArcaeaSim.Subsystems.Scores.Entities {
2 | ///
3 | ///
4 | ///
5 | /// Represents a sky note, which is associated to an arc note.
6 | ///
7 | public sealed class SkyNote : NoteBase, IHasTick {
8 |
9 | public int Tick { get; set; }
10 |
11 | public override NoteType Type => NoteType.Sky;
12 |
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/ArcaeaView/Subsystems/Scores/Entities/TimingNote.cs:
--------------------------------------------------------------------------------
1 | namespace Moe.Mottomo.ArcaeaSim.Subsystems.Scores.Entities {
2 | ///
3 | ///
4 | ///
5 | /// Represents a timing note.
6 | ///
7 | public sealed class TimingNote : NoteBase, IHasTick {
8 |
9 | public int Tick { get; set; }
10 |
11 | ///
12 | /// New tempo value.
13 | ///
14 | public float Bpm { get; set; }
15 |
16 | ///
17 | /// Beats per measure.
18 | ///
19 | public float BeatsPerMeasure { get; set; }
20 |
21 | public override NoteType Type => NoteType.Timing;
22 |
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/ArcaeaView/Subsystems/Scores/Entities/Track.cs:
--------------------------------------------------------------------------------
1 | namespace Moe.Mottomo.ArcaeaSim.Subsystems.Scores.Entities {
2 | ///
3 | /// Arcaea track.
4 | ///
5 | public enum Track {
6 |
7 | ///
8 | /// Not an option.
9 | ///
10 | Invalid = 0,
11 | ///
12 | /// The left track.
13 | ///
14 | Track1 = 1,
15 | ///
16 | /// The center-left track.
17 | ///
18 | Track2 = 2,
19 | ///
20 | /// The center-right track.
21 | ///
22 | Track3 = 3,
23 | ///
24 | /// The right track.
25 | ///
26 | Track4 = 4
27 |
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/ArcaeaView/Subsystems/Scores/IHasTick.cs:
--------------------------------------------------------------------------------
1 | namespace Moe.Mottomo.ArcaeaSim.Subsystems.Scores {
2 | ///
3 | /// A note with only one tick.
4 | ///
5 | public interface IHasTick {
6 |
7 | ///
8 | /// Tick of this note, in milliseconds.
9 | ///
10 | int Tick { get; }
11 |
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/ArcaeaView/Subsystems/Scores/IHasTicks.cs:
--------------------------------------------------------------------------------
1 | namespace Moe.Mottomo.ArcaeaSim.Subsystems.Scores {
2 | ///
3 | /// A note with a start tick and an end tick.
4 | ///
5 | public interface IHasTicks {
6 |
7 | ///
8 | /// Start tick, in milliseconds.
9 | ///
10 | int StartTick { get; }
11 |
12 | ///
13 | /// End tick, in milliseconds.
14 | ///
15 | int EndTick { get; }
16 |
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/ArcaeaView/Subsystems/Scores/IPreviewNote.cs:
--------------------------------------------------------------------------------
1 | namespace Moe.Mottomo.ArcaeaSim.Subsystems.Scores {
2 | ///
3 | /// A preview note with its Y position precalculated.
4 | ///
5 | public interface IPreviewNote {
6 |
7 | ///
8 | /// Precalculated Y position.
9 | ///
10 | float PreviewY { get; }
11 |
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/ArcaeaView/Subsystems/Scores/IRangedPreviewNote.cs:
--------------------------------------------------------------------------------
1 | namespace Moe.Mottomo.ArcaeaSim.Subsystems.Scores {
2 | ///
3 | /// A preview note with its Y positions at start and the end are precalculated.
4 | ///
5 | public interface IRangedPreviewNote {
6 |
7 | ///
8 | /// Precalculated starting Y position.
9 | ///
10 | float PreviewStartY { get; }
11 |
12 | ///
13 | /// Precalculated ending Y position.
14 | ///
15 | float PreviewEndY { get; }
16 |
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/ArcaeaView/Subsystems/Scores/Visualization/ArcEasingHelper.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Runtime.CompilerServices;
3 | using Microsoft.Xna.Framework;
4 | using Moe.Mottomo.ArcaeaSim.Core;
5 | using Moe.Mottomo.ArcaeaSim.Subsystems.Scores.Entities;
6 |
7 | namespace Moe.Mottomo.ArcaeaSim.Subsystems.Scores.Visualization {
8 | ///
9 | /// Arc note easing helper functions.
10 | ///
11 | public static class ArcEasingHelper {
12 |
13 | ///
14 | /// Calculates the eased value of a pair of coordinates.
15 | ///
16 | /// Start position.
17 | /// End position.
18 | /// A value increasing from 0 to 1 while moving from to .
19 | /// Easing method.
20 | /// Eased coordinate.
21 | public static Vector3 Ease(Vector3 start, Vector3 end, float t, ArcEasing easing) {
22 | t = MathHelper.Clamp(t, 0, 1);
23 |
24 | switch (easing) {
25 | case ArcEasing.S:
26 | return EaseLinear(start, end, t);
27 | case ArcEasing.CubicBezier:
28 | return EaseCubicBezier(start, end, t);
29 | case ArcEasing.Si:
30 | case ArcEasing.So:
31 | case ArcEasing.SiSo:
32 | case ArcEasing.SoSi:
33 | case ArcEasing.SoSo:
34 | case ArcEasing.SiSi:
35 | return EaseSinus(start, end, t, easing);
36 | default:
37 | throw new ArgumentOutOfRangeException(nameof(easing), easing, null);
38 | }
39 | }
40 |
41 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
42 | private static Vector3 EaseLinear(Vector3 p1, Vector3 p2, float t) {
43 | return Vector3.Lerp(p1, p2, t);
44 | }
45 |
46 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
47 | private static Vector3 EaseCubicBezier(Vector3 p1, Vector3 p2, float t) {
48 | var mt = 1 - t;
49 | var mt2 = mt * mt;
50 | var mt3 = mt2 * mt;
51 | var t2 = t * t;
52 | var t3 = t2 * t;
53 |
54 | var x = mt3 * p1.X + 3 * mt2 * t * p1.X + 3 * mt * t2 * p2.X + t3 * p2.X;
55 | var y = MathHelper.Lerp(p1.Y, p2.Y, t);
56 | var z = mt3 * p1.Z + 3 * mt2 * t * p1.Z + 3 * mt * t2 * p2.Z + t3 * p2.Z;
57 |
58 | return new Vector3(x, y, z);
59 | }
60 |
61 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
62 | private static Vector3 EaseSinus(Vector3 p1, Vector3 p2, float t, ArcEasing easing) {
63 | var y = MathHelper.Lerp(p1.Y, p2.Y, t);
64 |
65 | float sx, sz;
66 |
67 | switch (easing) {
68 | case ArcEasing.Si:
69 | case ArcEasing.SiSi:
70 | case ArcEasing.SiSo:
71 | sx = MathF.Sin(t * MathHelper.PiOver2);
72 | break;
73 | case ArcEasing.So:
74 | case ArcEasing.SoSi:
75 | case ArcEasing.SoSo:
76 | sx = 1 - MathF.Cos(t * MathHelper.PiOver2);
77 | break;
78 | default:
79 | throw new ArgumentOutOfRangeException(nameof(easing), easing, null);
80 | }
81 |
82 | switch (easing) {
83 | case ArcEasing.Si:
84 | case ArcEasing.So:
85 | // Credit: @18111398
86 | sz = t;
87 | break;
88 | case ArcEasing.SoSi:
89 | case ArcEasing.SiSi:
90 | sz = MathF.Sin(t * MathHelper.PiOver2);
91 | break;
92 | case ArcEasing.SiSo:
93 | case ArcEasing.SoSo:
94 | sz = 1 - MathF.Cos(t * MathHelper.PiOver2);
95 | break;
96 | default:
97 | throw new ArgumentOutOfRangeException(nameof(easing), easing, null);
98 | }
99 |
100 | var dx = p2.X - p1.X;
101 | var x = p1.X + dx * sx;
102 |
103 | var dz = p2.Z - p1.Z;
104 | var z = p1.Z + dz * sz;
105 |
106 | return new Vector3(x, y, z);
107 | }
108 |
109 | }
110 | }
111 |
--------------------------------------------------------------------------------
/ArcaeaView/Subsystems/Scores/Visualization/FloorVisualNote.cs:
--------------------------------------------------------------------------------
1 | using JetBrains.Annotations;
2 | using Microsoft.Xna.Framework;
3 | using Microsoft.Xna.Framework.Graphics;
4 | using Moe.Mottomo.ArcaeaSim.Subsystems.Rendering;
5 | using Moe.Mottomo.ArcaeaSim.Subsystems.Scores.Entities;
6 |
7 | namespace Moe.Mottomo.ArcaeaSim.Subsystems.Scores.Visualization {
8 | ///
9 | ///
10 | ///
11 | /// Represents a floor visual note.
12 | ///
13 | public sealed class FloorVisualNote : VisualNoteBase, IPreviewNote {
14 |
15 | public FloorVisualNote([NotNull] VisualBeatmap beatmap, [NotNull] FloorNote baseNote, [NotNull] StageMetrics metrics)
16 | : base(beatmap, baseNote) {
17 | _baseNote = baseNote;
18 | _metrics = metrics;
19 | _noteRectangle = new TexturedRectangle(beatmap.GraphicsDevice);
20 | _linkHexahedron = new ColoredHexahedron(beatmap.GraphicsDevice);
21 |
22 | PreviewY = beatmap.CalculateY(baseNote.Tick, metrics, metrics.FinishLineY);
23 | }
24 |
25 | public override void Draw(int beatmapTicks, float currentY) {
26 | var metrics = _metrics;
27 | var left = ((int)_baseNote.Track - 0.5f) * metrics.TrackInnerWidth / 4 - metrics.FloorNoteWidth / 2 - metrics.HalfTrackInnerWidth;
28 | var bottom = PreviewY - currentY;
29 |
30 | var bottomLeft = new Vector2(left, bottom);
31 | var size = new Vector2(metrics.FloorNoteWidth, metrics.FloorNoteHeight);
32 |
33 | _noteRectangle.SetVerticesXY(bottomLeft, size, Color.White, Z);
34 |
35 | var effect = (BasicEffect)NoteEffects.Effects[(int)_baseNote.Type];
36 |
37 | effect.Alpha = 0.75f;
38 |
39 | effect.TextureEnabled = true;
40 | effect.VertexColorEnabled = true;
41 |
42 | effect.Texture = _texture;
43 |
44 | _noteRectangle.Draw(effect.CurrentTechnique);
45 |
46 | var synced = SynchronizedSkyVisualNote;
47 |
48 | if (synced != null) {
49 | // Draw the sync line.
50 |
51 | var n = (ArcNote)synced.Parent.BaseNote;
52 |
53 | // TODO: This calculation can be executed when loading the beatmap, and store in some fields like "PreviewX".
54 |
55 | // var skyPoint = new Vector3(skyMiddle, skyBottom, skyEdge);
56 | // Optimization for sky point calculation needed
57 |
58 | var ratio = (float)(((SkyNote)synced.BaseNote).Tick - n.StartTick) / (n.EndTick - n.StartTick);
59 |
60 | var startX = (n.StartX - -0.5f) * metrics.TrackInnerWidth / (1.5f - -0.5f) - metrics.HalfTrackInnerWidth;
61 | var startZ = metrics.SkyInputZ * n.StartY + metrics.ArcHeightLowerBorder * (1 - n.StartY);
62 |
63 | var endX = (n.EndX - -0.5f) * metrics.TrackInnerWidth / (1.5f - -0.5f) - metrics.HalfTrackInnerWidth;
64 | var endZ = metrics.SkyInputZ * n.EndY + metrics.ArcHeightLowerBorder * (1 - n.EndY);
65 |
66 | var start = new Vector3(startX, 1, startZ);
67 | var end = new Vector3(endX, 1, endZ);
68 |
69 | var skyPoint = ArcEasingHelper.Ease(start, end, ratio, n.Easing);
70 | skyPoint.Y = synced.PreviewY - currentY;
71 |
72 | var thisPoint = new Vector3(left + metrics.FloorNoteWidth / 2, bottom, Z);
73 |
74 | _linkHexahedron.SetVerticesXZ(thisPoint, skyPoint, Color.PaleVioletRed, LinkSectionSize);
75 |
76 | effect.Alpha = 0.2f;
77 |
78 | effect.TextureEnabled = false;
79 | effect.VertexColorEnabled = true;
80 |
81 | _linkHexahedron.Draw(effect.CurrentTechnique);
82 | }
83 | }
84 |
85 | public override bool IsVisible(int beatmapTicks, float currentY) {
86 | if (_baseNote.Tick < beatmapTicks - _metrics.PastTickThreshold || beatmapTicks + _metrics.FutureTickThreshold < _baseNote.Tick) {
87 | return false;
88 | }
89 |
90 | return !(PreviewY < currentY || currentY + _metrics.TrackLength < PreviewY);
91 | }
92 |
93 | public void SetTexture([NotNull] Texture2D texture) {
94 | _texture = texture;
95 | }
96 |
97 | public float PreviewY { get; }
98 |
99 | [CanBeNull]
100 | public SkyVisualNote SynchronizedSkyVisualNote { get; internal set; }
101 |
102 | protected override void Dispose(bool disposing) {
103 | _noteRectangle?.Dispose();
104 | _noteRectangle = null;
105 | _linkHexahedron?.Dispose();
106 | _linkHexahedron = null;
107 | }
108 |
109 | private const float Z = 0.04f;
110 | private static readonly Vector2 LinkSectionSize = new Vector2(0.25f, 0.25f);
111 |
112 | private Texture2D _texture;
113 |
114 | private readonly FloorNote _baseNote;
115 | private readonly StageMetrics _metrics;
116 | private TexturedRectangle _noteRectangle;
117 | private ColoredHexahedron _linkHexahedron;
118 |
119 | }
120 | }
121 |
--------------------------------------------------------------------------------
/ArcaeaView/Subsystems/Scores/Visualization/IDrawableNote.cs:
--------------------------------------------------------------------------------
1 | namespace Moe.Mottomo.ArcaeaSim.Subsystems.Scores.Visualization {
2 | ///
3 | /// A drawable note protocol.
4 | ///
5 | public interface IDrawableNote {
6 |
7 | ///
8 | /// Draws this note.
9 | ///
10 | /// Current beatmap time, in milliseconds. Please note that beatmap time does not equal to audio time when a beatmap's audio offset is non-zero.
11 | /// Current Y position of the ongoing beatmap.
12 | void Draw(int beatmapTicks, float currentY);
13 |
14 | ///
15 | /// Determines whether this note is visible or not.
16 | ///
17 | /// Current beatmap time, in milliseconds. Please note that beatmap time does not equal to audio time when a beatmap's audio offset is non-zero.
18 | /// Current Y position of the ongoing beatmap.
19 | /// if the note is visible, otherwise .
20 | bool IsVisible(int beatmapTicks, float currentY);
21 |
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/ArcaeaView/Subsystems/Scores/Visualization/LongVisualNote.cs:
--------------------------------------------------------------------------------
1 | using JetBrains.Annotations;
2 | using Microsoft.Xna.Framework;
3 | using Microsoft.Xna.Framework.Graphics;
4 | using Moe.Mottomo.ArcaeaSim.Subsystems.Rendering;
5 | using Moe.Mottomo.ArcaeaSim.Subsystems.Scores.Entities;
6 |
7 | namespace Moe.Mottomo.ArcaeaSim.Subsystems.Scores.Visualization {
8 | ///
9 | ///
10 | ///
11 | /// Represents a long visual note.
12 | ///
13 | public sealed class LongVisualNote : VisualNoteBase, IRangedPreviewNote {
14 |
15 | public LongVisualNote([NotNull] VisualBeatmap beatmap, [NotNull] LongNote baseNote, [NotNull] StageMetrics metrics)
16 | : base(beatmap, baseNote) {
17 | _baseNote = baseNote;
18 | _metrics = metrics;
19 | _noteRectangle = new TexturedRectangle(beatmap.GraphicsDevice);
20 |
21 | PreviewStartY = beatmap.CalculateY(baseNote.StartTick, metrics, metrics.FinishLineY);
22 | PreviewEndY = beatmap.CalculateY(baseNote.EndTick, metrics, metrics.FinishLineY);
23 | }
24 |
25 | public override void Draw(int beatmapTicks, float currentY) {
26 | var left = ((int)_baseNote.Track - 0.5f) * _metrics.TrackInnerWidth / 4 - _metrics.FloorNoteWidth / 2 - _metrics.HalfTrackInnerWidth;
27 | var bottom = PreviewStartY - currentY;
28 |
29 | if (bottom < _metrics.FinishLineY) {
30 | bottom = _metrics.FinishLineY;
31 | }
32 |
33 | var top = PreviewEndY - currentY;
34 |
35 | if (top > _metrics.TrackLength) {
36 | top = _metrics.TrackLength;
37 | }
38 |
39 | var bottomLeft = new Vector2(left, bottom);
40 | var size = new Vector2(_metrics.FloorNoteWidth, top - bottom);
41 |
42 | _noteRectangle.SetVerticesXY(bottomLeft, size, Color.White, Z);
43 |
44 | var effect = (BasicEffect)NoteEffects.Effects[(int)_baseNote.Type];
45 |
46 | effect.Alpha = 0.75f;
47 |
48 | effect.TextureEnabled = true;
49 | effect.VertexColorEnabled = true;
50 |
51 | // Highlighted before the time reaches this note's start tick, and dim this note after that.
52 | // Correction credit: @18111398
53 | var isHighlighted = beatmapTicks >= _baseNote.StartTick;
54 | var texture = isHighlighted ? _hightlightedTexture : _texture;
55 |
56 | effect.Texture = texture;
57 |
58 | _noteRectangle.Draw(effect.CurrentTechnique);
59 | }
60 |
61 | public override bool IsVisible(int beatmapTicks, float currentY) {
62 | if (_baseNote.EndTick < beatmapTicks - _metrics.PastTickThreshold || beatmapTicks + _metrics.FutureTickThreshold < _baseNote.StartTick) {
63 | return false;
64 | }
65 |
66 | return !(PreviewEndY < currentY || currentY + _metrics.TrackLength < PreviewStartY);
67 | }
68 |
69 | public void SetTextures([NotNull] Texture2D texture, [NotNull] Texture2D highlightedTexture) {
70 | _texture = texture;
71 | _hightlightedTexture = highlightedTexture;
72 | }
73 |
74 | public float PreviewStartY { get; }
75 |
76 | public float PreviewEndY { get; }
77 |
78 | protected override void Dispose(bool disposing) {
79 | _noteRectangle?.Dispose();
80 | _noteRectangle = null;
81 | }
82 |
83 | private const float Z = 0.04f;
84 |
85 | private Texture2D _texture;
86 | private Texture2D _hightlightedTexture;
87 |
88 | private readonly LongNote _baseNote;
89 | private readonly StageMetrics _metrics;
90 | private TexturedRectangle _noteRectangle;
91 |
92 | }
93 | }
94 |
--------------------------------------------------------------------------------
/ArcaeaView/Subsystems/Scores/Visualization/NoteEffects.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.Xna.Framework.Graphics;
2 | using Moe.Mottomo.ArcaeaSim.Subsystems.Scores.Entities;
3 |
4 | namespace Moe.Mottomo.ArcaeaSim.Subsystems.Scores.Visualization {
5 | internal static class NoteEffects {
6 |
7 | internal static readonly Effect[] Effects = new Effect[(int)NoteType.Max + 1];
8 |
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/ArcaeaView/Subsystems/Scores/Visualization/SkyVisualNote.cs:
--------------------------------------------------------------------------------
1 | using JetBrains.Annotations;
2 | using Microsoft.Xna.Framework;
3 | using Microsoft.Xna.Framework.Graphics;
4 | using Moe.Mottomo.ArcaeaSim.Extensions;
5 | using Moe.Mottomo.ArcaeaSim.Subsystems.Rendering;
6 | using Moe.Mottomo.ArcaeaSim.Subsystems.Scores.Entities;
7 |
8 | namespace Moe.Mottomo.ArcaeaSim.Subsystems.Scores.Visualization {
9 | ///
10 | ///
11 | ///
12 | /// Represents a sky visual note.
13 | ///
14 | public sealed class SkyVisualNote : VisualNoteBase, IPreviewNote {
15 |
16 | public SkyVisualNote([NotNull] VisualBeatmap beatmap, [NotNull] SkyNote baseNote, [NotNull] ArcVisualNote parent, [NotNull] StageMetrics metrics)
17 | : base(beatmap, baseNote) {
18 | _metrics = metrics;
19 |
20 | Parent = parent;
21 |
22 | PreviewY = beatmap.CalculateY(baseNote.Tick, metrics, metrics.FinishLineY);
23 |
24 | var graphicsDevice = beatmap.GraphicsDevice;
25 | _graphicsDevice = graphicsDevice;
26 |
27 | _vertexBuffer1 = new VertexBuffer(graphicsDevice, VertexPositionColorTexture.VertexDeclaration, 16, BufferUsage.WriteOnly);
28 | _indexBuffer1 = new IndexBuffer(graphicsDevice, IndexElementSize.SixteenBits, 24, BufferUsage.WriteOnly);
29 |
30 | _vertexBuffer2 = new VertexBuffer(graphicsDevice, VertexPositionColorTexture.VertexDeclaration, 8, BufferUsage.WriteOnly);
31 | _indexBuffer2 = new IndexBuffer(graphicsDevice, IndexElementSize.SixteenBits, 12, BufferUsage.WriteOnly);
32 |
33 | var indices1 = new ushort[] {
34 | // bottom, top
35 | 0, 1, 2,
36 | 2, 1, 3,
37 | 4, 5, 6,
38 | 6, 5, 7,
39 |
40 | // front, back
41 | 8, 9, 10,
42 | 10, 9, 11,
43 | 12, 13, 14,
44 | 14, 13, 15
45 | };
46 |
47 | _indexBuffer1.SetData(indices1);
48 |
49 | var indices2 = new ushort[] {
50 | 0, 1, 2,
51 | 2, 1, 3,
52 | 4, 5, 6,
53 | 6, 5, 7,
54 | };
55 |
56 | _indexBuffer2.SetData(indices2);
57 |
58 | _shadowRectangle = new ColoredRectangle(graphicsDevice);
59 | }
60 |
61 | public ArcVisualNote Parent { get; }
62 |
63 | public float PreviewY { get; }
64 |
65 | public override bool IsVisible(int beatmapTicks, float currentY) {
66 | var baseNote = (SkyNote)BaseNote;
67 |
68 | if (baseNote.Tick < beatmapTicks - _metrics.PastTickThreshold || beatmapTicks + _metrics.FutureTickThreshold < baseNote.Tick) {
69 | return false;
70 | }
71 |
72 | return !(PreviewY < currentY || currentY + _metrics.TrackLength < PreviewY);
73 | }
74 |
75 | public override void Draw(int beatmapTicks, float currentY) {
76 | var effect = (BasicEffect)NoteEffects.Effects[(int)BaseNote.Type];
77 |
78 | effect.TextureEnabled = true;
79 | effect.VertexColorEnabled = true;
80 | effect.Alpha = 1;
81 |
82 | effect.Texture = _texture;
83 |
84 | _graphicsDevice.SetVertexBuffer(_vertexBuffer1);
85 | _graphicsDevice.Indices = _indexBuffer1;
86 |
87 | foreach (var pass in effect.CurrentTechnique.Passes) {
88 | pass.Apply();
89 | _graphicsDevice.DrawIndexedPrimitives(PrimitiveType.TriangleList, 0, 0, 8);
90 | }
91 |
92 | effect.Texture = _texture;
93 |
94 | _graphicsDevice.SetVertexBuffer(_vertexBuffer2);
95 | _graphicsDevice.Indices = _indexBuffer2;
96 |
97 | foreach (var pass in effect.CurrentTechnique.Passes) {
98 | pass.Apply();
99 | _graphicsDevice.DrawIndexedPrimitives(PrimitiveType.TriangleList, 0, 0, 4);
100 | }
101 |
102 | // Cast shadow
103 | effect.TextureEnabled = false;
104 | effect.VertexColorEnabled = true;
105 | effect.Alpha = 0.75f;
106 |
107 | _shadowRectangle.Draw(effect.CurrentTechnique);
108 | }
109 |
110 | public void SetVertices(Vector3 bottomNearLeft, Vector3 size) {
111 | // Tint color
112 | var color = Color.White;
113 |
114 | var vertices1 = new[] {
115 | // bottom
116 | new VertexPositionColorTexture {Position = new Vector3(bottomNearLeft.X, bottomNearLeft.Y, bottomNearLeft.Z), Color = color, TextureCoordinate = new Vector2(0, 1)},
117 | new VertexPositionColorTexture {Position = new Vector3(bottomNearLeft.X + size.X, bottomNearLeft.Y, bottomNearLeft.Z), Color = color, TextureCoordinate = new Vector2(1, 1)},
118 | new VertexPositionColorTexture {Position = new Vector3(bottomNearLeft.X, bottomNearLeft.Y + size.Y, bottomNearLeft.Z), Color = color, TextureCoordinate = new Vector2(0, 0)},
119 | new VertexPositionColorTexture {Position = new Vector3(bottomNearLeft.X + size.X, bottomNearLeft.Y + size.Y, bottomNearLeft.Z), Color = color, TextureCoordinate = new Vector2(1, 0)},
120 |
121 | // up
122 | new VertexPositionColorTexture {Position = new Vector3(bottomNearLeft.X, bottomNearLeft.Y, bottomNearLeft.Z + size.Z), Color = color, TextureCoordinate = new Vector2(0, 1)},
123 | new VertexPositionColorTexture {Position = new Vector3(bottomNearLeft.X + size.X, bottomNearLeft.Y, bottomNearLeft.Z + size.Z), Color = color, TextureCoordinate = new Vector2(1, 1)},
124 | new VertexPositionColorTexture {Position = new Vector3(bottomNearLeft.X, bottomNearLeft.Y + size.Y, bottomNearLeft.Z + size.Z), Color = color, TextureCoordinate = new Vector2(0, 0)},
125 | new VertexPositionColorTexture {Position = new Vector3(bottomNearLeft.X + size.X, bottomNearLeft.Y + size.Y, bottomNearLeft.Z + size.Z), Color = color, TextureCoordinate = new Vector2(1, 0)},
126 |
127 | // front
128 | new VertexPositionColorTexture {Position = new Vector3(bottomNearLeft.X, bottomNearLeft.Y, bottomNearLeft.Z), Color = color, TextureCoordinate = new Vector2(0, 1)},
129 | new VertexPositionColorTexture {Position = new Vector3(bottomNearLeft.X + size.X, bottomNearLeft.Y, bottomNearLeft.Z), Color = color, TextureCoordinate = new Vector2(1, 1)},
130 | new VertexPositionColorTexture {Position = new Vector3(bottomNearLeft.X, bottomNearLeft.Y, bottomNearLeft.Z + size.Z), Color = color, TextureCoordinate = new Vector2(0, 0)},
131 | new VertexPositionColorTexture {Position = new Vector3(bottomNearLeft.X + size.X, bottomNearLeft.Y, bottomNearLeft.Z + size.Z), Color = color, TextureCoordinate = new Vector2(1, 0)},
132 |
133 | // back
134 | new VertexPositionColorTexture {Position = new Vector3(bottomNearLeft.X, bottomNearLeft.Y + size.Y, bottomNearLeft.Z), Color = color, TextureCoordinate = new Vector2(0, 1)},
135 | new VertexPositionColorTexture {Position = new Vector3(bottomNearLeft.X + size.X, bottomNearLeft.Y + size.Y, bottomNearLeft.Z), Color = color, TextureCoordinate = new Vector2(1, 1)},
136 | new VertexPositionColorTexture {Position = new Vector3(bottomNearLeft.X, bottomNearLeft.Y + size.Y, bottomNearLeft.Z + size.Z), Color = color, TextureCoordinate = new Vector2(0, 0)},
137 | new VertexPositionColorTexture {Position = new Vector3(bottomNearLeft.X + size.X, bottomNearLeft.Y + size.Y, bottomNearLeft.Z + size.Z), Color = color, TextureCoordinate = new Vector2(1, 0)}
138 | };
139 |
140 | _vertexBuffer1.SetData(vertices1);
141 |
142 | var vertices2 = new[] {
143 | // left
144 | new VertexPositionColorTexture {Position = new Vector3(bottomNearLeft.X, bottomNearLeft.Y, bottomNearLeft.Z), Color = color, TextureCoordinate = new Vector2(0, 1)},
145 | new VertexPositionColorTexture {Position = new Vector3(bottomNearLeft.X, bottomNearLeft.Y + size.Y, bottomNearLeft.Z), Color = color, TextureCoordinate = new Vector2(1, 1)},
146 | new VertexPositionColorTexture {Position = new Vector3(bottomNearLeft.X, bottomNearLeft.Y, bottomNearLeft.Z + size.Z), Color = color, TextureCoordinate = new Vector2(0, 0)},
147 | new VertexPositionColorTexture {Position = new Vector3(bottomNearLeft.X, bottomNearLeft.Y + size.Y, bottomNearLeft.Z + size.Z), Color = color, TextureCoordinate = new Vector2(1, 0)},
148 |
149 | // right
150 | new VertexPositionColorTexture {Position = new Vector3(bottomNearLeft.X + size.X, bottomNearLeft.Y, bottomNearLeft.Z), Color = color, TextureCoordinate = new Vector2(0, 1)},
151 | new VertexPositionColorTexture {Position = new Vector3(bottomNearLeft.X + size.X, bottomNearLeft.Y + size.Y, bottomNearLeft.Z), Color = color, TextureCoordinate = new Vector2(1, 1)},
152 | new VertexPositionColorTexture {Position = new Vector3(bottomNearLeft.X + size.X, bottomNearLeft.Y, bottomNearLeft.Z + size.Z), Color = color, TextureCoordinate = new Vector2(0, 0)},
153 | new VertexPositionColorTexture {Position = new Vector3(bottomNearLeft.X + size.X, bottomNearLeft.Y + size.Y, bottomNearLeft.Z + size.Z), Color = color, TextureCoordinate = new Vector2(1, 0)}
154 | };
155 |
156 | _vertexBuffer2.SetData(vertices2);
157 |
158 | _shadowRectangle.SetVerticesXY(bottomNearLeft.XY(), size.XY(), Color.Gray, ShadowZ);
159 | }
160 |
161 | public void SetTexture([NotNull] Texture2D texture) {
162 | _texture = texture;
163 | }
164 |
165 | protected override void Dispose(bool disposing) {
166 | _vertexBuffer1?.Dispose();
167 | _indexBuffer1?.Dispose();
168 | _vertexBuffer2?.Dispose();
169 | _indexBuffer2?.Dispose();
170 | _vertexBuffer1 = null;
171 | _indexBuffer1 = null;
172 | _vertexBuffer2 = null;
173 | _indexBuffer2 = null;
174 |
175 | _shadowRectangle?.Dispose();
176 | _shadowRectangle = null;
177 | }
178 |
179 | private const float ShadowZ = 0.03f;
180 |
181 | private Texture2D _texture;
182 |
183 | private readonly StageMetrics _metrics;
184 |
185 | private readonly GraphicsDevice _graphicsDevice;
186 | private VertexBuffer _vertexBuffer1;
187 | private IndexBuffer _indexBuffer1;
188 | private VertexBuffer _vertexBuffer2;
189 | private IndexBuffer _indexBuffer2;
190 |
191 | private ColoredRectangle _shadowRectangle;
192 |
193 | }
194 | }
195 |
--------------------------------------------------------------------------------
/ArcaeaView/Subsystems/Scores/Visualization/StageMetrics.cs:
--------------------------------------------------------------------------------
1 | using System.Runtime.CompilerServices;
2 | using JetBrains.Annotations;
3 |
4 | namespace Moe.Mottomo.ArcaeaSim.Subsystems.Scores.Visualization {
5 | ///
6 | /// Stage metrics that controls the general track layout.
7 | ///
8 | ///
9 | /// This type is class rather than structure is because the same instance is frequently passed between objects.
10 | /// So one reference on the heap is enough, and can avoid extra stack allocation overhead.
11 | ///
12 | [NotNull]
13 | public sealed class StageMetrics {
14 |
15 | ///
16 | /// Creates a new instance.
17 | ///
18 | public StageMetrics() {
19 | TrackLength = 100;
20 | TrackInnerWidth = 20;
21 |
22 | LaneDividerWidth = 0.2f;
23 |
24 | FloorNoteWidth = TrackInnerWidth / 48 * 11;
25 | FloorNoteHeight = TrackInnerWidth / 8;
26 |
27 | SkyNoteWidth = TrackInnerWidth / 4;
28 | SkyNoteHeight = SkyNoteWidth / 4;
29 | SkyNoteTallness = SkyNoteHeight / 2;
30 |
31 | FinishLineY = 10;
32 | FinishLineHeight = 2f;
33 |
34 | SkyInputZ = TrackInnerWidth / 3.236f;
35 |
36 | SkyInputWidth = TrackInnerWidth * 1.25f;
37 | SkyInputTallness = 0.8f;
38 |
39 | PlayableArcWidth = 2f;
40 | PlayableArcTallness = 2f;
41 |
42 | TraceArcWidth = PlayableArcWidth / 6;
43 | TraceArcTallness = PlayableArcTallness / 6;
44 |
45 | ArcHeightLowerBorder = 0.75f;
46 |
47 |
48 | PastTickThreshold = 0;
49 | FutureTickThreshold = 4000;
50 |
51 | Speed = 5;
52 | }
53 |
54 | ///
55 | /// Length of the track.
56 | ///
57 | public float TrackLength { get; set; }
58 |
59 | ///
60 | /// Full width of the track.
61 | ///
62 | public float TrackFullWidth {
63 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
64 | get => TrackInnerWidth / (1 - (72f / 1024)); // this ratio can be measured from track texture
65 | }
66 |
67 | ///
68 | /// Half of the full width of the track.
69 | ///
70 | public float HalfTrackFullWidth {
71 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
72 | get => TrackFullWidth / 2;
73 | }
74 |
75 | ///
76 | /// Inner width of the track (excluding the borders).
77 | ///
78 | public float TrackInnerWidth { get; set; }
79 |
80 | ///
81 | /// Half of the inner width of the track.
82 | ///
83 | public float HalfTrackInnerWidth {
84 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
85 | get => TrackInnerWidth / 2;
86 | }
87 |
88 | ///
89 | /// Width of a lane divider.
90 | ///
91 | public float LaneDividerWidth { get; set; }
92 |
93 | ///
94 | /// Width of a floor note.
95 | ///
96 | public float FloorNoteWidth { get; set; }
97 |
98 | ///
99 | /// Height of a floor note.
100 | ///
101 | public float FloorNoteHeight { get; set; }
102 |
103 | ///
104 | /// Width of a sky note.
105 | ///
106 | public float SkyNoteWidth { get; set; }
107 |
108 | ///
109 | /// Height of a sky note.
110 | ///
111 | public float SkyNoteHeight { get; set; }
112 |
113 | ///
114 | /// Tallness of a sky note.
115 | ///
116 | public float SkyNoteTallness { get; set; }
117 |
118 | ///
119 | /// Y position of the finish line.
120 | ///
121 | public float FinishLineY { get; set; }
122 |
123 | ///
124 | /// Height of the finish line.
125 | ///
126 | public float FinishLineHeight { get; set; }
127 |
128 | ///
129 | /// Z position of the sky input.
130 | ///
131 | public float SkyInputZ { get; set; }
132 |
133 | ///
134 | /// Width of the sky input.
135 | ///
136 | public float SkyInputWidth { get; set; }
137 |
138 | ///
139 | /// Tallness of the sky input.
140 | ///
141 | public float SkyInputTallness { get; set; }
142 |
143 | ///
144 | /// Playable arc section width.
145 | ///
146 | public float PlayableArcWidth { get; set; }
147 |
148 | ///
149 | /// Playable arc section tallness.
150 | ///
151 | public float PlayableArcTallness { get; set; }
152 |
153 | ///
154 | /// Trace arc section width.
155 | ///
156 | public float TraceArcWidth { get; set; }
157 |
158 | ///
159 | /// Trace arc tallness.
160 | ///
161 | public float TraceArcTallness { get; set; }
162 |
163 | ///
164 | /// The height of an arc when its z == 0.
165 | ///
166 | public float ArcHeightLowerBorder { get; set; }
167 |
168 | ///
169 | /// Past tick threshold, in milliseconds. Notes whose tick or ending tick is before beatmapTick + PastTickThreshold
will not be drawn.
170 | ///
171 | public int PastTickThreshold { get; set; }
172 |
173 | ///
174 | /// Future tick threshold, in milliseconds. Notes whose tick or starting tick is after beatmapTick + FutureTickThreshold
will not be drawn.
175 | ///
176 | ///
177 | /// Calculating this value is challenging.
178 | /// A value too large will significantly decrease the rendering efficiency.
179 | /// A value too small will cause notes after a sudden tempo change disappear.
180 | /// This value is related to . When increases, the required value of increases.
181 | ///
182 | public int FutureTickThreshold { get; set; }
183 |
184 | ///
185 | /// Note falling speed. Corresponding to the setting in Arcaea.
186 | ///
187 | public float Speed { get; set; }
188 |
189 | }
190 | }
191 |
--------------------------------------------------------------------------------
/ArcaeaView/Subsystems/Scores/Visualization/VisualBeatmap.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Linq;
3 | using JetBrains.Annotations;
4 | using Microsoft.Xna.Framework.Graphics;
5 | using Moe.Mottomo.ArcaeaSim.Subsystems.Scores.Entities;
6 | using OpenMLTD.MilliSim.Core;
7 |
8 | namespace Moe.Mottomo.ArcaeaSim.Subsystems.Scores.Visualization {
9 | ///
10 | ///
11 | /// A visual beatmap.
12 | ///
13 | public sealed class VisualBeatmap : DisposableBase {
14 |
15 | ///
16 | /// Creates a new instance from a using specified stage metrics.
17 | ///
18 | /// A used to draw the beatmap's notes.
19 | ///
20 | /// Stage metrics.
21 | public VisualBeatmap([NotNull] GraphicsDevice graphicsDevice, [NotNull] Beatmap baseBeatmap, [NotNull] StageMetrics metrics) {
22 | GraphicsDevice = graphicsDevice;
23 | BaseBeatmap = baseBeatmap;
24 |
25 | Timings = baseBeatmap.Notes.Where(n => n.Type == NoteType.Timing).Cast().ToArray();
26 |
27 | var visualNotes = baseBeatmap.Notes.Where(n => n.Type != NoteType.Timing).Select(n => {
28 | switch (n.Type) {
29 | case NoteType.Floor:
30 | return new FloorVisualNote(this, (FloorNote)n, metrics);
31 | case NoteType.Long:
32 | return new LongVisualNote(this, (LongNote)n, metrics);
33 | case NoteType.Arc:
34 | return new ArcVisualNote(this, (ArcNote)n, metrics);
35 | default:
36 | throw new ArgumentOutOfRangeException();
37 | }
38 | }).ToArray();
39 |
40 | VisualNotes = visualNotes;
41 |
42 | // Scan sync sky notes and add lines between the floor note and sky note.
43 | // Credits: @money, @RW, @JDF, @L-F0rce
44 |
45 | var allArcVisualNotes = visualNotes.Where(note => note.Type == NoteType.Arc).Cast().ToArray();
46 |
47 | for (var i = 0; i < visualNotes.Length; ++i) {
48 | var visualNote = visualNotes[i];
49 |
50 | if (visualNote.Type != NoteType.Floor || i >= visualNotes.Length - 1) {
51 | continue;
52 | }
53 |
54 | var n = (FloorVisualNote)visualNote;
55 | var floorBase = (FloorNote)n.BaseNote;
56 |
57 | // TODO: Naive method. Should be optimized.
58 | foreach (var arcVisualNote in allArcVisualNotes) {
59 | if (arcVisualNote.SkyVisualNotes == null || arcVisualNote.SkyVisualNotes.Length == 0) {
60 | continue;
61 | }
62 |
63 | foreach (var skyVisualNote in arcVisualNote.SkyVisualNotes) {
64 | if (Math.Abs(((SkyNote)skyVisualNote.BaseNote).Tick - floorBase.Tick) <= 1) {
65 | n.SynchronizedSkyVisualNote = skyVisualNote;
66 | break;
67 | }
68 | }
69 |
70 | if (n.SynchronizedSkyVisualNote != null) {
71 | break;
72 | }
73 | }
74 | }
75 |
76 | // "Supports"
77 | foreach (var arcVisualNote in allArcVisualNotes) {
78 | var n = (ArcNote)arcVisualNote.BaseNote;
79 |
80 | var ancestor = allArcVisualNotes.FirstOrDefault(note => {
81 | var anotherArcNote = (ArcNote)note.BaseNote;
82 | return anotherArcNote.EndTick == n.StartTick && anotherArcNote.IsPlayable && anotherArcNote.Color == n.Color;
83 | });
84 |
85 | if (ancestor == null) {
86 | arcVisualNote.ShouldDrawSupport = true;
87 | arcVisualNote.ShouldDrawHeader = true;
88 | continue;
89 | }
90 |
91 | if (!((ArcNote)ancestor.BaseNote).StartY.Equals(((ArcNote)ancestor.BaseNote).EndY)) {
92 | arcVisualNote.ShouldDrawSupport = true;
93 | }
94 | }
95 | }
96 |
97 | ///
98 | /// Gets the underlying .
99 | ///
100 | [NotNull]
101 | public Beatmap BaseBeatmap { get; }
102 |
103 | ///
104 | /// Gets the used to draw this beatmap's notes.
105 | ///
106 | [NotNull]
107 | public GraphicsDevice GraphicsDevice { get; }
108 |
109 | ///
110 | /// Gets all visual notes in the beatmap.
111 | ///
112 | [NotNull, ItemNotNull]
113 | public VisualNoteBase[] VisualNotes { get; }
114 |
115 | ///
116 | /// Gets all timing notes in the beatmap.
117 | ///
118 | [NotNull, ItemNotNull]
119 | public TimingNote[] Timings { get; }
120 |
121 | ///
122 | /// Gets the first tempo value of this beatmap.
123 | ///
124 | public float FirstBpm {
125 | get {
126 | if (_firstBpm == null) {
127 | if (Timings.Length == 0) {
128 | throw new FormatException("No timing note found in this beatmap.");
129 | }
130 |
131 | _firstBpm = Timings[0].Bpm;
132 | }
133 |
134 | return _firstBpm.Value;
135 | }
136 | }
137 |
138 | ///
139 | /// Calculate a beatmap time's Y distance according to timing and metrics.
140 | ///
141 | /// Current beatmap time, in milliseconds. Please note that beatmap time does not equal to audio time when a beatmap's audio offset is non-zero.
142 | /// Stage metrics.
143 | /// The final addition value. This value is directly added to the calculated value. Example usage: assign this parameter the finishing line's Y distance.
144 | ///
145 | public float CalculateY(int beatmapTicks, [NotNull] StageMetrics metrics, float addition) {
146 | const float timeScale = 0.6f;
147 |
148 | var timingNotes = Timings;
149 |
150 | if (timingNotes.Length == 0) {
151 | return 0;
152 | }
153 |
154 | var measureLength = metrics.FloorNoteHeight * 6;
155 |
156 | var result = 0f;
157 |
158 | for (var i = 0; i < timingNotes.Length; ++i) {
159 | var timingNote = timingNotes[i];
160 |
161 | if (beatmapTicks < timingNote.Tick) {
162 | break;
163 | }
164 |
165 | if (i >= timingNotes.Length - 1 || beatmapTicks < timingNotes[i + 1].Tick) {
166 | result += (beatmapTicks - timingNote.Tick) * timingNote.Bpm;
167 | } else {
168 | result += (timingNotes[i + 1].Tick - timingNote.Tick) * timingNote.Bpm;
169 | }
170 | }
171 |
172 | result /= 100_000f;
173 |
174 | result *= measureLength * timeScale * metrics.Speed;
175 |
176 | result += addition;
177 |
178 | return result;
179 | }
180 |
181 | protected override void Dispose(bool disposing) {
182 | if (VisualNotes.Length > 0) {
183 | foreach (var note in VisualNotes) {
184 | note.Dispose();
185 | }
186 | }
187 | }
188 |
189 | private float? _firstBpm;
190 |
191 | }
192 | }
193 |
--------------------------------------------------------------------------------
/ArcaeaView/Subsystems/Scores/Visualization/VisualNoteBase.cs:
--------------------------------------------------------------------------------
1 | using JetBrains.Annotations;
2 | using Microsoft.Xna.Framework.Graphics;
3 | using Moe.Mottomo.ArcaeaSim.Subsystems.Scores.Entities;
4 | using OpenMLTD.MilliSim.Core;
5 |
6 | namespace Moe.Mottomo.ArcaeaSim.Subsystems.Scores.Visualization {
7 | ///
8 | ///
9 | ///
10 | /// Visual note base.
11 | ///
12 | public abstract class VisualNoteBase : DisposableBase, IDrawableNote {
13 |
14 | protected VisualNoteBase([NotNull] VisualBeatmap beatmap, [NotNull] NoteBase baseNote) {
15 | Beatmap = beatmap;
16 | BaseNote = baseNote;
17 | Type = baseNote.Type;
18 | }
19 |
20 | ///
21 | /// Gets the that this note belongs to.
22 | ///
23 | public VisualBeatmap Beatmap { get; }
24 |
25 | ///
26 | /// Gets the underlying .
27 | ///
28 | public NoteBase BaseNote { get; }
29 |
30 | ///
31 | /// Gets the type of this note.
32 | ///
33 | public NoteType Type { get; }
34 |
35 | public abstract void Draw(int beatmapTicks, float currentY);
36 |
37 | public abstract bool IsVisible(int beatmapTicks, float currentY);
38 |
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/ArcaeaView/packages.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | The Clear BSD License
2 |
3 | Copyright (c) 2018 OpenMLTD
4 | All rights reserved.
5 |
6 | Redistribution and use in source and binary forms, with or without
7 | modification, are permitted (subject to the limitations in the disclaimer
8 | below) provided that the following conditions are met:
9 |
10 | * Redistributions of source code must retain the above copyright notice,
11 | this list of conditions and the following disclaimer.
12 |
13 | * Redistributions in binary form must reproduce the above copyright
14 | notice, this list of conditions and the following disclaimer in the
15 | documentation and/or other materials provided with the distribution.
16 |
17 | * Neither the name of the copyright holder nor the names of its
18 | contributors may be used to endorse or promote products derived from this
19 | software without specific prior written permission.
20 |
21 | NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY
22 | THIS LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
23 | CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
24 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
25 | PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
26 | CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
27 | EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
28 | PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
29 | BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
30 | IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
31 | ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
32 | POSSIBILITY OF SUCH DAMAGE.
33 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # ArcaeaSim
2 |
3 | An [Arcaea](https://arcaea.lowiro.com/) simulator for viewing; no playing support is planned in a near future.
4 |
5 | ArcaeaSim is built on [MilliSim](https://github.com/hozuki/MilliSim). Thus this is also a demonstration of the possibility of MilliSim.
6 |
7 | ArcaeaSim is in alpha phase so please don't expect a splendid visual effect.
8 | However, neither is it expected to go beyond the line to be a clone of Arcaea.
9 |
10 | - [Demo video 1](https://www.bilibili.com/video/av19881005/) (even before the initial commit; a very early technical preview so it may not look very appealing)
11 | - [Demo video 2](https://www.bilibili.com/video/av19981419/) (that's better)
12 | - [Demo video 3](https://www.bilibili.com/video/av20162946/) (even better)
13 |
14 | **NOTICE:** ArcaeaSim was once taken down by [DMCA request](https://github.com/github/dmca/blob/master/2018/2018-03-15-Arcaea.md) because of using copyrighted materials.
15 | In newer commits (i.e. after 15th March 2018) those materials are all removed, so when you run the application you will not see a UI like in those videos.
16 |
17 | | Downloads| |
18 | |--|--|
19 | | [GitHub Releases](https://github.com/hozuki/ArcaeaSim2/releases) |   |
20 | | [AppVeyor](https://ci.appveyor.com/api/projects/hozuki/ArcaeaSim2/artifacts/arcaeasim-appveyor-latest.zip) | (latest development build) |
21 |
22 | | Build Status | |
23 | |--|--|
24 | | AppVeyor | [](https://ci.appveyor.com/project/hozuki/ArcaeaSim2) |
25 |
26 | **Stage:** alpha
27 |
28 | **Miscellaneous:**
29 |
30 | [](https://github.com/hozuki/ArcaeaSim2/graphs/contributors)
31 | [](https://github.com/hozuki/ArcaeaSim2)
32 | [](LICENSE.txt)
33 |
34 | ## Usage
35 |
36 | *This section is for end users, using packaged binaries and resources.*
37 |
38 | Like MilliSim, you will need:
39 |
40 | - Operating System: Windows 7 SP1 or later
41 | - [.NET Framework 4.5](https://www.microsoft.com/en-us/download/details.aspx?id=42642)
42 | - [Visual C++ 2015 Runtime](https://www.microsoft.com/en-us/download/details.aspx?id=53587)
43 | - OpenAL (bundled OpenAL-Soft Win32 build in newer releases)
44 |
45 | Test song(s) included in the repo:
46 |
47 | | Song | Cover | Composer(s) | Beatmap Author(s) |
48 | |---|---|---|---|
49 | | The Silence |
| JDF | JDF |
50 |
51 | 1. You can set the window properties (e.g. window size) in `Contents/app.config.yml`.
52 | 2. You can set the background image properties in `Contents/config/background_image.yml`.
53 | 3. You can configure which beatmap to load in `Contents/config/beatmap_loader.yml`.
54 | 4. You can set the background music properties in `Contents/config/background_music.yml`.
55 | 5. You can filter out the plugins that you don't want (e.g. the debug info overlay) by commenting them out (adding a "#" at the start of the line) in `Contents/plugins.yml`. But remember to keep the essential plugins.
56 |
57 | Run the app with `--debug` switch to enable debug mode.
58 |
59 | **Known issue(s)**:
60 |
61 | - Some native Arcaea MP3s contain bad blocks (block sizes are inconsistent). When playing these songs ArcaeaSim will stop working and exit immediately.
62 | If you turn on the debug mode you will see the MP3 format issue in error log. In this case, you need to convert these MP3 files using other tools
63 | such as Foobar2000 and Audition, and feed ArcaeaSim with converted MP3 files.
64 | - Windows 7 (w/ SP1) may raise [ACM](https://msdn.microsoft.com/en-us/library/windows/desktop/dd742945.aspx) "conversion not possible" exception if you play the MP3 files.
65 | You can try to convert them to other formats, such as Wave audio files, and modify `audio_controller.yml` to load the converted files.
66 | - [Some other possible reasons of ArcaeaSim not starting](https://github.com/hozuki/MilliSim#note-if-millisim-cannot-launch).
67 |
68 | ## Development
69 |
70 | *This section is for developers.*
71 |
72 | Before you start, please read [Starting.md](docs/Starting.md).
73 |
74 | For building the solution, please read [Building.md](docs/Building.md).
75 |
76 | ## Contributing
77 |
78 | Contributions (especially PRs) are welcome because I probably do not have much time digging too deep into this, or simply doing maintenance work.
79 | Therefore it is not possible to make it good-looking (like that in Arcaea) only by myself.
80 |
81 | Remember to fork this repo, create your own feature branch, and make a PR. Thank you.
82 |
83 | Use English wherever you can. This makes it easier for contributors from various places to collaborate. :)
84 |
85 | ## License
86 |
87 | The code is open-sourced under BSD 3-Clause Clear license.
88 |
89 | > I REMADE ALL IMAGES BY MYSELF, USED A CUSTOM SONG AND BEATMAP WHICH ARE GRANTED BY THEIR AUTHOR.
90 | > THERE SHOULD BE NO COPYRIGHT ISSUES NOW.
--------------------------------------------------------------------------------
/appveyor.yml:
--------------------------------------------------------------------------------
1 | # https://www.appveyor.com/docs/appveyor-yml/
2 |
3 | version: 1.0.3.{build}
4 | image: Visual Studio 2017
5 | platform:
6 | - x86
7 | configuration:
8 | - Release
9 | environment:
10 | EnableNuGetPackageRestore: true
11 | Release_Suffix: -alpha
12 | before_build:
13 | - cmd: nuget update -self
14 | - cmd: git submodule update --init --recursive
15 | - cmd: cd thirdparty\MilliSim
16 | - cmd: npm install
17 | - cmd: cd ..\..
18 | - cmd: npm install -g marked
19 | - cmd: node thirdparty\MilliSim\scripts\nuget_restore.js
20 | - cmd: cd thirdparty\MilliSim
21 | - cmd: node scripts\patch_asminfo.js
22 | - cmd: cd ..\..
23 | - ps: .\thirdparty\MilliSim\scripts\install_monogame.ps1
24 | build_script:
25 | - cmd: msbuild ArcaeaSim.sln /p:Configuration=Release /verbosity:minimal /p:nowarn="1574,1591"
26 | after_build:
27 | - ps: .\thirdparty\MilliSim\scripts\extract-openal-soft.ps1 -destination $Env:APPVEYOR_BUILD_FOLDER\ArcaeaSim\bin\Windows\x86\$Env:CONFIGURATION\
28 | - cmd: marked ./README.md -o ./README.html
29 | - cmd: cd thirdparty\MilliSim
30 | - cmd: node scripts\appveyor_copy_licenses.js
31 | - cmd: cd ..\..
32 | - cmd: del %APPVEYOR_BUILD_FOLDER%\ArcaeaSim\bin\Windows\x86\%CONFIGURATION%\*.xml
33 | - cmd: del %APPVEYOR_BUILD_FOLDER%\ArcaeaSim\bin\Windows\x86\%CONFIGURATION%\*.pdb
34 | # Remove useless SkiaSharp native libraries (we only need the one in x86 dir)
35 | - cmd: del %APPVEYOR_BUILD_FOLDER%\ArcaeaSim\bin\Windows\x86\%CONFIGURATION%\libSkiaSharp.*
36 | - cmd: copy %APPVEYOR_BUILD_FOLDER%\ArcaeaSim\bin\Windows\x86\%CONFIGURATION%\x86\libSkiaSharp.dll %APPVEYOR_BUILD_FOLDER%\ArcaeaSim\bin\Windows\x86\%CONFIGURATION%\
37 | # Remove useless x64 dir (Direct3D branch targets x86)
38 | - cmd: rmdir /s /q %APPVEYOR_BUILD_FOLDER%\ArcaeaSim\bin\Windows\x86\%CONFIGURATION%\x64
39 | # x86 dir is not need either (and we don't have FFmpeg here)
40 | - cmd: rmdir /s /q %APPVEYOR_BUILD_FOLDER%\ArcaeaSim\bin\Windows\x86\%CONFIGURATION%\x86
41 | - cmd: 7z a arcaeasim.zip -r %APPVEYOR_BUILD_FOLDER%\ArcaeaSim\bin\Windows\x86\%CONFIGURATION%\*
42 | - cmd: 7z a arcaeasim.zip %APPVEYOR_BUILD_FOLDER%\README.html
43 | - cmd: cd thirdparty\MilliSim
44 | - cmd: 7z a arcaeasim.zip -r docs\licenses\*
45 | - cmd: cd ..\..
46 | - cmd: copy arcaeasim.zip arcaeasim-appveyor-latest.zip
47 | - cmd: copy arcaeasim.zip arcaeasim-appveyor-v%APPVEYOR_BUILD_VERSION%%RELEASE_SUFFIX%.zip
48 | test: off
49 | artifacts:
50 | - path: arcaeasim-appveyor-v%APPVEYOR_BUILD_VERSION%%RELEASE_SUFFIX%.zip
51 | name: WithVersion
52 | - path: arcaeasim-appveyor-latest.zip
53 | name: Static
54 | deploy:
55 | - provider: GitHub
56 | description: ArcaeaSim
57 | auth_token:
58 | secure: u+MFjjY665AT4PHJ2bFMINBLnnnwSj1jLnNeFZoWfeYbomTNSPOmdahffmZa+dRH
59 | artifact: /arcaeasim.+\.zip/
60 | draft: false
61 | prerelease: true
62 | on:
63 | appveyor_repo_tag: true
64 |
--------------------------------------------------------------------------------
/docs/Building.md:
--------------------------------------------------------------------------------
1 | # Building ArcaeaSim
2 |
3 | **Requirements:**
4 |
5 | - OS: Windows
6 | - Compiler and Toolchain:
7 | - Visual Studio 2017 Community
8 | - .NET Framework 4.5 Toolchain
9 | - [NuGet CLI](https://www.nuget.org/downloads) (≥ 4.3.0)
10 |
11 | > **Remember** to [update your NuGet version](https://docs.microsoft.com/en-us/nuget/guides/install-nuget) before building.
12 | > Otherwise you are very likely to see errors like "Too many projects specified".
13 |
14 | **Step 1**: Clone this repo:
15 |
16 | ```bash
17 | git clone https://github.com/hozuki/ArcaeaSim.git
18 | cd ArcaeaSim
19 | git submodule update --init --recursive
20 | ```
21 |
22 | **Step 2**: Install [MonoGame](http://www.monogame.net/downloads/) (version 3.6 or later).
23 |
24 | **Step 3**: Build the solution in Visual Studio.
25 |
26 | If you encounter errors like "assembly not found", try to run `nuget restore SOLUTION_NAME.sln` in every solution directory.
27 | The automatic NuGet restore in Visual Studio does not work.
28 |
29 | **Step 4**: A little cleaning. In the directory containing built binaries, replace the `libSkiaSharp.dll` with the DLL with the same name
30 | under `x86` directory.
31 |
32 | The default `libSkiaSharp.dll` in the binaries directory is a 64-bit DLL, but MonoGame WindowsDX build only produces a 32-bit executable by default,
33 | so the executable cannot load the default DLL.
34 |
--------------------------------------------------------------------------------
/docs/Starting.md:
--------------------------------------------------------------------------------
1 | # Starting to Work
2 |
3 | ## Conventions
4 |
5 | The standard vocabulary for calling notes etc. follows the names on [Wikia](http://lowiro.wikia.com/wiki/Arcaea_Wiki).
6 |
7 | The tips below are to help you understand the coding conventions in this repo.
8 |
9 | For coding style, use:
10 |
11 | - `_camelCaseWithAPrefixUnderscore` for private instance members (`private`, `private static`, and `private readonly`);
12 | - `camelCase` for parameters and local variables;
13 | - `PascalCase` for the rest, including `private static readonly`.
14 |
15 | Visibility order: `public` -> `protected` -> `private`
16 |
17 | ## Coordinate Systems
18 |
19 | Arcaea's beatmap coordinate system and Arcaea's coordinate system (in code): left hand, Y-up (OpenGL coordinate system).
20 |
21 | ```plain
22 | Y
23 | Z |
24 | \--X
25 | ```
26 |
27 | ArcaeaSim's coordinate system (in code): right hand, Z-up (Direct3D and mathematical coordinate system).
28 |
29 | ```plain
30 | Z
31 | Y |
32 | \--X
33 | ```
34 |
--------------------------------------------------------------------------------