using System; using System.Collections.Generic; using System.Linq; using System.Runtime.InteropServices; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Input; using Newtonsoft.Json; using ShiftOS.Engine; using ShiftOS.Frontend.GraphicsSubsystem; namespace ShiftOS.Frontend { /// /// This is the main type for your game. /// public class ShiftOS : Game { internal GraphicsDeviceManager graphicsDevice; SpriteBatch spriteBatch; private bool DisplayDebugInfo = false; public ShiftOS() { graphicsDevice = new GraphicsDeviceManager(this); var uconf = Objects.UserConfig.Get(); graphicsDevice.PreferredBackBufferHeight = uconf.ScreenHeight; graphicsDevice.PreferredBackBufferWidth = uconf.ScreenWidth; SkinEngine.SkinLoaded += () => { UIManager.ResetSkinTextures(GraphicsDevice); UIManager.InvalidateAll(); }; UIManager.Viewport = new System.Drawing.Size( graphicsDevice.PreferredBackBufferWidth, graphicsDevice.PreferredBackBufferHeight ); Content.RootDirectory = "Content"; //Make window borderless Window.IsBorderless = false; //Set the title Window.Title = "ShiftOS"; //Fullscreen graphicsDevice.IsFullScreen = uconf.Fullscreen; UIManager.Init(this); } private Keys lastKey = Keys.None; /// /// Allows the game to perform any initialization it needs to before starting to run. /// This is where it can query for any required services and load any non-graphic /// related content. Calling base.Initialize will enumerate through any components /// and initialize them as well. /// protected override void Initialize() { OutOfBoxExperience.Init(new MonoGameOOBE()); //Before we do ANYTHING, we've got to initiate the ShiftOS engine. UIManager.GraphicsDevice = GraphicsDevice; //Let's get localization going. Localization.RegisterProvider(new MonoGameLanguageProvider()); //First things first, let's initiate the window manager. AppearanceManager.Initiate(new Desktop.WindowManager()); //Cool. Now the engine's window management system talks to us. //Also initiate the desktop Engine.Desktop.Init(new Desktop.Desktop()); //While we're having a damn initiation fuckfest, let's get the hacking engine running. Hacking.Initiate(); //Now we can initiate the Infobox subsystem Engine.Infobox.Init(new Infobox()); //Let's initiate the engine just for a ha. TerminalBackend.TerminalRequested += () => { AppearanceManager.SetupWindow(new Apps.Terminal()); }; FileSkimmerBackend.Init(new MGFSLayer()); //Create a main menu var mm = new MainMenu(); UIManager.AddTopLevel(mm); base.Initialize(); } private double timeSinceLastPurge = 0; private Texture2D MouseTexture = null; /// /// LoadContent will be called once per game and is the place to load /// all of your content. /// protected override void LoadContent() { // Create a new SpriteBatch, which can be used to draw textures. this.spriteBatch = new SpriteBatch(base.GraphicsDevice); UIManager.ResetSkinTextures(GraphicsDevice); // TODO: use this.Content to load your game content here var bmp = Properties.Resources.cursor_9x_pointer; var _lock = bmp.LockBits(new System.Drawing.Rectangle(0, 0, bmp.Width, bmp.Height), System.Drawing.Imaging.ImageLockMode.ReadOnly, System.Drawing.Imaging.PixelFormat.Format32bppArgb); byte[] rgb = new byte[Math.Abs(_lock.Stride) * _lock.Height]; Marshal.Copy(_lock.Scan0, rgb, 0, rgb.Length); bmp.UnlockBits(_lock); MouseTexture = new Texture2D(GraphicsDevice, bmp.Width, bmp.Height); MouseTexture.SetData(rgb); rgb = null; } /// /// UnloadContent will be called once per game and is the place to unload /// game-specific content. /// protected override void UnloadContent() { MouseTexture = null; // TODO: Unload any non ContentManager content here } private double kb_elapsedms = 0; private double mouseMS = 0; private MouseState LastMouseState; /// /// Allows the game to run logic such as updating the world, /// checking for collisions, gathering input, and playing audio. /// /// Provides a snapshot of timing values. protected override void Update(GameTime gameTime) { if (UIManager.CrossThreadOperations.Count > 0) { var action = UIManager.CrossThreadOperations.Dequeue(); action?.Invoke(); } //Let's get the mouse state var mouseState = Mouse.GetState(this.Window); LastMouseState = mouseState; UIManager.ProcessMouseState(LastMouseState, mouseMS); if (mouseState.LeftButton == ButtonState.Pressed) { mouseMS = 0; } else { mouseMS += gameTime.ElapsedGameTime.TotalMilliseconds; } //So we have mouse input, and the UI layout system working... //But an OS isn't useful without the keyboard! //Let's see how keyboard input works. //Hmmm... just like the mouse... var keystate = Keyboard.GetState(); //Simple... just iterate through this list and generate some key events? var keys = keystate.GetPressedKeys(); if (keys.Length > 0) { var key = keys.FirstOrDefault(x => x != Keys.LeftControl && x != Keys.RightControl && x != Keys.LeftShift && x != Keys.RightShift && x != Keys.LeftAlt && x != Keys.RightAlt); if(lastKey != key) { kb_elapsedms = 0; lastKey = key; } } if (keystate.IsKeyDown(lastKey)) { if (kb_elapsedms == 0 || kb_elapsedms >= 500) { if (lastKey == Keys.F11) { UIManager.Fullscreen = !UIManager.Fullscreen; } else { var shift = keystate.IsKeyDown(Keys.LeftShift) || keystate.IsKeyDown(Keys.RightShift); var alt = keystate.IsKeyDown(Keys.LeftAlt) || keystate.IsKeyDown(Keys.RightAlt); var control = keystate.IsKeyDown(Keys.LeftControl) || keystate.IsKeyDown(Keys.RightControl); if (control && lastKey == Keys.D) { DisplayDebugInfo = !DisplayDebugInfo; } else if(control && lastKey == Keys.E) { UIManager.ExperimentalEffects = !UIManager.ExperimentalEffects; } else { var e = new KeyEvent(control, alt, shift, lastKey); UIManager.ProcessKeyEvent(e); } } } kb_elapsedms += gameTime.ElapsedGameTime.TotalMilliseconds; } else { kb_elapsedms = 0; } //Cause layout update on all elements UIManager.LayoutUpdate(gameTime); timeSinceLastPurge += gameTime.ElapsedGameTime.TotalSeconds; if(timeSinceLastPurge > 2) { GraphicsContext.StringCaches.Clear(); timeSinceLastPurge = 0; GC.Collect(); } //Some hackables have a connection timeout applied to them. //We must update timeout values here, and disconnect if the timeout //hits zero. if(Hacking.CurrentHackable != null) { if (Hacking.CurrentHackable.DoConnectionTimeout) { Hacking.CurrentHackable.MillisecondsCountdown -= gameTime.ElapsedGameTime.TotalMilliseconds; if(Hacking.CurrentHackable.MillisecondsCountdown <= 0) { Hacking.FailHack(); } } } base.Update(gameTime); } private GUI.TextControl framerate = new GUI.TextControl(); /// /// This is called when the game should draw itself. /// /// Provides a snapshot of timing values. protected override void Draw(GameTime gameTime) { UIManager.DrawControlsToTargets(GraphicsDevice, spriteBatch, 0, 0); var rasterizerState = new RasterizerState(); rasterizerState.CullMode = CullMode.None; rasterizerState.MultiSampleAntiAlias = true; spriteBatch.Begin(SpriteSortMode.Immediate, BlendState.NonPremultiplied, SamplerState.LinearClamp, DepthStencilState.Default, rasterizerState); //Draw the desktop BG. UIManager.DrawBackgroundLayer(GraphicsDevice, spriteBatch, 640, 480); //The desktop is drawn, now we can draw the UI. UIManager.DrawTArgets(spriteBatch); //Draw a mouse cursor var mousepos = Mouse.GetState(this.Window).Position; spriteBatch.Draw(MouseTexture, new Rectangle(mousepos.X+1, mousepos.Y+1, MouseTexture.Width, MouseTexture.Height), Color.Black * 0.5f); spriteBatch.Draw(MouseTexture, new Rectangle(mousepos.X, mousepos.Y, MouseTexture.Width, MouseTexture.Height), Color.White); if(Hacking.CurrentHackable != null) { if (Hacking.CurrentHackable.DoConnectionTimeout) { string str = $"Timeout in {(Hacking.CurrentHackable.MillisecondsCountdown / 1000).ToString("#.##")} seconds."; var gfx = new GraphicsContext(GraphicsDevice, spriteBatch, 0, 0, UIManager.Viewport.Width, UIManager.Viewport.Height); var measure = gfx.MeasureString(str, SkinEngine.LoadedSkin.HeaderFont); gfx.DrawString(str, 5, (gfx.Height - ((int)measure.Y) - 5), Color.Red, SkinEngine.LoadedSkin.HeaderFont); } } if (DisplayDebugInfo) { var gfxContext = new GraphicsContext(GraphicsDevice, spriteBatch, 0, 0, graphicsDevice.PreferredBackBufferWidth, graphicsDevice.PreferredBackBufferHeight); var color = Color.White; double fps = Math.Round(1 / gameTime.ElapsedGameTime.TotalSeconds); if (fps <= 20) color = Color.Red; gfxContext.DrawString($@"ShiftOS ======================= Copyright (c) 2017 ShiftOS Developers Debug information - {fps} FPS CTRL+D: toggle debug menu CTRL+E: toggle experimental effects (experimental effects enabled: {UIManager.ExperimentalEffects}) Use the ""debug"" Terminal Command for engine debug commands. Red text means low framerate, a low framerate could be a sign of CPU hogging code or a memory leak. Text cache: {GraphicsContext.StringCaches.Count}", 0, 0, color, new System.Drawing.Font("Lucida Console", 9F, System.Drawing.FontStyle.Bold)); } spriteBatch.End(); base.Draw(gameTime); } } [ShiftoriumProvider] public class MonoGameShiftoriumProvider : IShiftoriumProvider { public List GetDefaults() { return JsonConvert.DeserializeObject>(Properties.Resources.Shiftorium); } } public class MGFSLayer : IFileSkimmer { public string GetFileExtension(FileType fileType) { switch (fileType) { case FileType.CommandFormat: return ".cf"; case FileType.Executable: return ".saa"; case FileType.Filesystem: return ".mfs"; case FileType.Image: return ".png"; case FileType.JSON: return ".json"; case FileType.Lua: return ".lua"; case FileType.Python: return ".py"; case FileType.Skin: return ".skn"; case FileType.TextFile: return ".txt"; default: return ".scrtm"; } } public void GetPath(string[] filetypes, FileOpenerStyle style, Action callback) { throw new NotImplementedException(); } public void OpenDirectory(string path) { throw new NotImplementedException(); } } }