-
Notifications
You must be signed in to change notification settings - Fork 16
/
EndlessGame.cs
292 lines (251 loc) · 12 KB
/
EndlessGame.cs
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
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
using System;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using AutomaticTypeMapper;
using EndlessClient.Audio;
using EndlessClient.Content;
using EndlessClient.ControlSets;
using EndlessClient.Rendering;
using EndlessClient.Rendering.Chat;
using EndlessClient.Test;
using EndlessClient.UIControls;
using EOLib;
using EOLib.Config;
using EOLib.Graphics;
using EOLib.IO;
using EOLib.IO.Actions;
using EOLib.Logger;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using MonoGame.Extended.BitmapFonts;
using MonoGame.Extended.Input;
namespace EndlessClient.GameExecution
{
[MappedType(BaseType = typeof(IEndlessGame), IsSingleton = true)]
public class EndlessGame : Game, IEndlessGame
{
private readonly IClientWindowSizeRepository _windowSizeRepository;
private readonly IContentProvider _contentProvider;
private readonly IGraphicsDeviceRepository _graphicsDeviceRepository;
private readonly IGameWindowRepository _gameWindowRepository;
private readonly IControlSetRepository _controlSetRepository;
private readonly IControlSetFactory _controlSetFactory;
private readonly ITestModeLauncher _testModeLauncher;
private readonly IPubFileLoadActions _pubFileLoadActions;
private readonly ILoggerProvider _loggerProvider;
private readonly IChatBubbleTextureProvider _chatBubbleTextureProvider;
private readonly IShaderRepository _shaderRepository;
private readonly IConfigurationProvider _configurationProvider;
private readonly IMfxPlayer _mfxPlayer;
private readonly IXnaControlSoundMapper _soundMapper;
private GraphicsDeviceManager _graphicsDeviceManager;
private KeyboardState _previousKeyState;
private TimeSpan _lastFrameUpdate;
#if DEBUG
private SpriteBatch _spriteBatch;
private Stopwatch _lastFrameRenderTime = Stopwatch.StartNew();
private int _frames, _displayFrames;
private Texture2D _black;
#endif
public EndlessGame(IClientWindowSizeRepository windowSizeRepository,
IContentProvider contentProvider,
IGraphicsDeviceRepository graphicsDeviceRepository,
IGameWindowRepository gameWindowRepository,
IControlSetRepository controlSetRepository,
IControlSetFactory controlSetFactory,
ITestModeLauncher testModeLauncher,
IPubFileLoadActions pubFileLoadActions,
ILoggerProvider loggerProvider,
IChatBubbleTextureProvider chatBubbleTextureProvider,
IShaderRepository shaderRepository,
IConfigurationProvider configurationProvider,
IMfxPlayer mfxPlayer,
IXnaControlSoundMapper soundMapper)
{
_windowSizeRepository = windowSizeRepository;
_contentProvider = contentProvider;
_graphicsDeviceRepository = graphicsDeviceRepository;
_gameWindowRepository = gameWindowRepository;
_controlSetRepository = controlSetRepository;
_controlSetFactory = controlSetFactory;
_testModeLauncher = testModeLauncher;
_pubFileLoadActions = pubFileLoadActions;
_loggerProvider = loggerProvider;
_chatBubbleTextureProvider = chatBubbleTextureProvider;
_shaderRepository = shaderRepository;
_configurationProvider = configurationProvider;
_mfxPlayer = mfxPlayer;
_soundMapper = soundMapper;
_graphicsDeviceManager = new GraphicsDeviceManager(this)
{
PreferredBackBufferWidth = ClientWindowSizeRepository.DEFAULT_BACKBUFFER_WIDTH,
PreferredBackBufferHeight = ClientWindowSizeRepository.DEFAULT_BACKBUFFER_HEIGHT
};
Content.RootDirectory = "Content";
}
protected override void Initialize()
{
Components.ComponentAdded += (o, e) =>
{
// this is bad hack
// all pre-game controls have a specific sound that should be mapped to them.
// in-game controls get their sounds mapped individually.
//
// Checking for GameStates.LoggedIn because the in-game controls are
// added to the components in the LoggedIn state
if (_controlSetRepository.CurrentControlSet.GameState != GameStates.LoggedIn)
{
_soundMapper.BindSoundToControl(e.GameComponent);
}
};
base.Initialize();
IsMouseVisible = true;
IsFixedTimeStep = false;
TargetElapsedTime = TimeSpan.FromMilliseconds(12);
_previousKeyState = Keyboard.GetState();
// setting Width/Height in window size repository applies the change to disable vsync
_graphicsDeviceManager.SynchronizeWithVerticalRetrace = false;
_graphicsDeviceManager.IsFullScreen = false;
_windowSizeRepository.Width = ClientWindowSizeRepository.DEFAULT_BACKBUFFER_WIDTH;
_windowSizeRepository.Height = ClientWindowSizeRepository.DEFAULT_BACKBUFFER_HEIGHT;
_windowSizeRepository.GameWindowSizeChanged += (_, _) =>
{
if (_windowSizeRepository.Width < ClientWindowSizeRepository.DEFAULT_BACKBUFFER_HEIGHT)
_windowSizeRepository.Width = ClientWindowSizeRepository.DEFAULT_BACKBUFFER_WIDTH;
if (_windowSizeRepository.Height < ClientWindowSizeRepository.DEFAULT_BACKBUFFER_HEIGHT)
_windowSizeRepository.Height = ClientWindowSizeRepository.DEFAULT_BACKBUFFER_HEIGHT;
};
Exiting += (_, _) => _mfxPlayer.StopBackgroundMusic();
}
protected override void LoadContent()
{
#if DEBUG
_spriteBatch = new SpriteBatch(GraphicsDevice);
_black = new Texture2D(GraphicsDevice, 1, 1);
_black.SetData(new[] { Color.Black });
#endif
_contentProvider.Load();
//todo: all the things that should load stuff as part of game's load/initialize should be broken into a pattern
_chatBubbleTextureProvider.LoadContent();
//the GraphicsDevice/Window don't exist until Initialize() is called by the framework
//Ideally, this would be set in a DependencyContainer, but I'm not sure of a way to do that now
_graphicsDeviceRepository.GraphicsDevice = GraphicsDevice;
_graphicsDeviceRepository.GraphicsDeviceManager = _graphicsDeviceManager;
_gameWindowRepository.Window = Window;
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
{
if (!File.Exists(ShaderRepository.HairClipFile))
{
throw new FileNotFoundException("Missing HairClip shader");
}
var shaderBytes = File.ReadAllBytes(ShaderRepository.HairClipFile);
_shaderRepository.Shaders[ShaderRepository.HairClip] = new Effect(GraphicsDevice, shaderBytes);
}
SetUpInitialControlSet();
if (_configurationProvider.MusicEnabled)
{
_mfxPlayer.PlayBackgroundMusic(1, EOLib.IO.Map.MusicControl.InterruptPlayRepeat);
}
AttemptToLoadPubFiles();
base.LoadContent();
}
protected override void Update(GameTime gameTime)
{
// Force updates to wait every 12ms
// Some game components rely on slower update times. 60FPS was the original, but 12ms factors nicely in 120ms "ticks"
// See: https://github.com/ethanmoffat/EndlessClient/issues/199
// Using IsFixedTimeStep = true with TargetUpdateTime set to 60FPS also limits the draw rate, which is not desired
if ((gameTime.TotalGameTime - _lastFrameUpdate).TotalMilliseconds >= 12.0)
{
#if DEBUG
var currentKeyState = Keyboard.GetState();
if (KeyboardExtended.GetState().WasKeyJustDown(Keys.F5))
{
_testModeLauncher.LaunchTestMode();
}
_previousKeyState = currentKeyState;
#endif
try
{
base.Update(gameTime);
}
catch (InvalidOperationException ioe) when (ioe.InnerException is NullReferenceException)
{
// hide "failed to compare two elements in the array" error from Monogame
}
_lastFrameUpdate = gameTime.TotalGameTime;
}
}
protected override void Draw(GameTime gameTime)
{
var isTestMode = _controlSetRepository.CurrentControlSet.GameState == GameStates.TestMode;
GraphicsDevice.Clear(isTestMode ? Color.White : Color.Black);
base.Draw(gameTime);
#if DEBUG
_frames++;
var fpsString = $"FPS: {_displayFrames}{(gameTime.IsRunningSlowly ? " (SLOW)" : string.Empty)}";
var dim = _contentProvider.Fonts[Constants.FontSize09].MeasureString(fpsString);
_spriteBatch.Begin();
_spriteBatch.Draw(_black, new Rectangle(18, 18, (int)dim.Width + 4, (int)dim.Height + 4), Color.White);
_spriteBatch.DrawString(_contentProvider.Fonts[Constants.FontSize09], fpsString, new Vector2(20, 20), Color.White);
_spriteBatch.End();
if (_lastFrameRenderTime.ElapsedMilliseconds > 1000)
{
_displayFrames = _frames;
_frames = 0;
_lastFrameRenderTime = Stopwatch.StartNew();
}
#endif
}
private void AttemptToLoadPubFiles()
{
const string PUB_LOG_MSG = "**** Unable to load default PUB file: {0}. Exception message: {1}";
try
{
_pubFileLoadActions.LoadItemFile(rangedWeaponIds: Constants.RangedWeaponIDs.Concat(Constants.InstrumentIDs));
}
catch (Exception ex) when (ex is IOException || ex is ArgumentException)
{
_loggerProvider.Logger.Log(PUB_LOG_MSG, string.Format(PubFileNameConstants.EIFFormat, 1), ex.Message);
}
try
{
_pubFileLoadActions.LoadNPCFile();
}
catch (Exception ex) when (ex is IOException || ex is ArgumentException)
{
_loggerProvider.Logger.Log(PUB_LOG_MSG, string.Format(PubFileNameConstants.ENFFormat, 1), ex.Message);
}
try
{
_pubFileLoadActions.LoadSpellFile();
}
catch (Exception ex) when (ex is IOException || ex is ArgumentException)
{
_loggerProvider.Logger.Log(PUB_LOG_MSG, string.Format(PubFileNameConstants.ESFFormat, 1), ex.Message);
}
try
{
_pubFileLoadActions.LoadClassFile();
}
catch (Exception ex) when (ex is IOException || ex is ArgumentException)
{
_loggerProvider.Logger.Log(PUB_LOG_MSG, string.Format(PubFileNameConstants.ECFFormat, 1), ex.Message);
}
}
private void SetUpInitialControlSet()
{
var controls = _controlSetFactory.CreateControlsForState(
GameStates.Initial,
_controlSetRepository.CurrentControlSet);
_controlSetRepository.CurrentControlSet = controls;
//since the controls are being created in Initialize(), adding them to the default game
// doesn't call the Initialize() method on any controls, so it must be done here
foreach (var xnaControl in _controlSetRepository.CurrentControlSet.AllComponents)
xnaControl.Initialize();
}
}
}