/* * GenericGameManager.cs * Authors: August Zinsser * * Copyright Matthew Belmonte 2007 */ using System; using System.Diagnostics; using System.Collections.Generic; using System.IO; using System.Text; using System.Security; using System.Security.Cryptography; using System.Runtime.InteropServices; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Audio; using Microsoft.Xna.Framework.Content; using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Input; using Microsoft.Xna.Framework.Storage; using LuaInterface; using Pina3D.Particles; using Pina3D; using Nuclex.Fonts; namespace tAC_Engine { /// /// Provides basic functionality for an XNA game such as Update() and Render(), plus also /// provides common utility functions used by the tAC_Engine such as ElapsedSeconds. /// public class GenericGameManager : Microsoft.Xna.Framework.Game { // Regular member variables protected GraphicsDeviceManager mGraphics; // Provides access to the graphics buffer protected ContentManager mContent; // Loads assets into XNA's content pipeline protected SpriteBatch mSpriteBatch; // Writes images to the graphics buffer protected Lua mLuaGameSettingsVM; // The lua virutal machine that saves global settings. // Other LuaVMs may be created and destroyed, but this one // should persist throughout the entire game protected bool mInitialized; // True when the game manager is finished with initialization protected Random mRandom; // Generates random numbers protected bool mIsLab; // Whether this game is running in the lab or on a standard/public machine protected bool mPauseGames; // Lets games or scripts "pause" the main game or minigames protected BitmapFont mStandardFont; // Font to use for standard text protected BitmapFont mStandardFontBig; // A bigger version of the standard text protected BitmapFont mDialogLabelFont; // Font to use for dialog labels protected BitmapFont mDialogTextFont; // Font to use for dialog text protected BitmapFont mSpaceFontBig; // A big scifi font protected BitmapFont mSpaceFont; // A regular-sized '' protected BitmapFont mInGameFontTiny; // Font to use in the playing area of games (like pickups) protected GameTime mLastGameTimeSnapshot; // Provides access to the time at the last game cycle protected bool mShowTimePass; // Display performance information protected float mUpdateTT = 0; // The total time elapsed so far protected float mInputDT; // The time elapsed since that last input cycle (should be much smaller than mLogicDT) protected float mLogicTimer; // Counts down the seconds until the next logic cycle should be run protected float mLastLogicTime; // The time of the last logic cycle protected float mLogicDT; // Time elapsed since the last logic cycle (should be much larger than mInputDT) protected bool mLogicUpdate; // Allows child classes to determine whether or not to update themselves protected float mLogicWaitTime = 1000f / 60f; // The period of each logic update cycle, typically the same as the draw cycle protected int mTimePassSamples = 100; // The number of samples to use for displaying averaged debugging timepass information protected LinkedList mInputDTs; // Saves the last InputDTs to average over for debugging information protected LinkedList mLogicDTs; // Saves the last LogicDTs to average over for debugging information protected LinkedList mDrawDTs; // Saves the last DrawDTs to average over for debugging information protected int mScreenWidth; // In pixels protected int mScreenHeight; // '' protected bool mFullScreen; // Windowed mode or not // Phase Queues (read in from a config file) to be parsed when the appropriate minigame is created protected Dictionary mPhaseQueues = new Dictionary(); protected Dictionary mPhaseQueuesCopy = new Dictionary(); /// /// How often (in milliseconds) the game performs logic updates /// public float LogicUpdateTargetTime { set { mLogicWaitTime = value; mLogicTimer = value; } get { return mLogicWaitTime; } } /// /// Temporarily keeps games from receiving their Update and Draw calls /// public bool PauseGames { set { mPauseGames = value; } get { return mPauseGames; } } /// /// Number of seconds that have elapsed between the most current logic update and its predecessor /// public float ElapsedSeconds { get { return mLogicDT / 1000f; } } /// /// Number of milliseconds that have elapsed between the most current logic update and its predecessor /// public float ElapsedMilliseconds { get { return mLogicDT; } } /// /// Number of seconds that have passed since the game started (updated once every input timepass) /// public float TotalSeconds { get { return (float)mLastGameTimeSnapshot.TotalGameTime.TotalSeconds; } } public int ScreenWidth { get { return mScreenWidth; } } public int ScreenHeight { get { return mScreenHeight; } } public float ScreenPhysicalWidth { set { mLuaGameSettingsVM["ScreenPhysicalWidth"] = value; } get { return (float)(double)mLuaGameSettingsVM["ScreenPhysicalWidth"]; } } public float ScreenPhysicalHeight { set { mLuaGameSettingsVM["ScreenPhysicalHeight"] = value; } get { return (float)(double)mLuaGameSettingsVM["ScreenPhysicalHeight"]; } } public float ScreenPhysicalDistance { set { mLuaGameSettingsVM["ScreenPhysicalDistance"] = value; } get { return (float)(double)mLuaGameSettingsVM["ScreenPhysicalDistance"]; } } public bool FullScreen { get { return mFullScreen; } } /// /// Whether or not to show debugging timepass variables /// public bool ShowTimePass { set { mShowTimePass = value; } get { return mShowTimePass; } } public ContentManager ContentManager { get { return mContent; } } public GraphicsDevice GraphicsDevice { get { return mGraphics.GraphicsDevice; } } public GraphicsDeviceManager GraphicsDeviceManager { get { return mGraphics; } } /// /// True if this game is running in the laboratory to collect EEG data /// public bool IsLab { get { return mIsLab; } } public bool IsInitialized { get { return mInitialized; } } /// /// A handle to the lua virtual machine that controls global game settings. Persists throughout the entire game. /// public Lua LuaGameManagerVM { get { return mLuaGameSettingsVM; } } public BitmapFont StandardFont { get { return mStandardFont; } } public BitmapFont StandardFontBig { get { return mStandardFontBig; } } public BitmapFont DialogTextFont { get { return mDialogTextFont; } } public BitmapFont DialogLabelFont { get { return mDialogLabelFont; } } public BitmapFont SpaceFontBig { get { return mSpaceFontBig; } } public BitmapFont SpaceFont { get { return mSpaceFont; } } public BitmapFont InGameFontTiny { get { return mInGameFontTiny; } } /// /// Returns a random number between 0 (inclusive) and 1 (exclusive) /// /// public float RandomNumber() { return (float)mRandom.NextDouble(); } /// /// Returns a random number between min (inclusive) and max (exclusive) /// /// /// /// public float RandomNumber(float min, float max) { return (float)(mRandom.NextDouble() * (max - min)) + min; } /// /// Returns a random number between the given min (inclusive) and max (inclusive) /// /// /// /// public int RandomNumber(int min, int max) { return (int)(mRandom.NextDouble() * (max - min + 1)) + min; } /// /// Returns a random number between 0 (inclusive) and 1 (exclusive). Calling this function multiple /// times with the same seed will return the same number. /// CAUTION: This function is too slow to use for real-time calculations. Only use it for precalculations! /// /// /// public float SeededRandomNumber(int seed) { Random random = new Random(seed); return (float)random.NextDouble(); } /// /// Retrieves the generic phase queue for the specified minigame /// /// Name of the minigame /// Generic Phase Queue for that minigame public GenericPhaseQueue GetPhaseQueueForMiniGame(string miniGameName) { //Copy the phase queue from the duplicate copy mPhaseQueues[miniGameName] = new GenericPhaseQueue(mPhaseQueuesCopy[miniGameName]); if (!mPhaseQueues.ContainsKey(miniGameName)) PrintAndDie("Game phase queue for game " + miniGameName + " was not set in config file."); return mPhaseQueues[miniGameName]; } /// /// Removes any phase queue associated with the specified minigame and then sets that game's phase queue /// /// public void SetPhaseQueueForMiniGame(string miniGameName, GenericPhaseQueue phaseQueue) { if (mPhaseQueues.ContainsKey(miniGameName)) mPhaseQueues.Remove(miniGameName); mPhaseQueues.Add(miniGameName, phaseQueue); mPhaseQueuesCopy.Add(miniGameName, phaseQueue); } /// /// Default constructor /// public GenericGameManager() { // Initialize general components mGraphics = new GraphicsDeviceManager(this); mContent = new ContentManager(Services); mLuaGameSettingsVM = new Lua(); mRandom = new Random(); mInitialized = false; } /// /// 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() { // Assume this game isn't in the lab unless the lab config file is found mIsLab = false; // Register Lua Callbacks LuaHelper.RegisterLuaCallbacks(this); // Load tweakable parameters LoadConfigFile(); // Initialize 3D engine Pina.Initialize( this, mGraphics, mContent, (float)(Math.PI / 4.0), 100.0f, 10000.0f, new Vector3(0.0f, -250.0f, 100.0f), new Vector3(0.0f, 0.0f, 25.0f), 10000); // Setup the values to smooth out timepass display mInputDTs = new LinkedList(); mLogicDTs = new LinkedList(); mDrawDTs = new LinkedList(); for (int i = 0; i < mTimePassSamples; i++) { mInputDTs.AddLast(0f); mLogicDTs.AddLast(0f); mDrawDTs.AddLast(0f); } base.Initialize(); SoundManager.Initialize(); mInitialized = true; } /// /// Load your graphics content. If loadAllContent is true, you should /// load content from both ResourceManagementMode pools. Otherwise, just /// load ResourceManagementMode.Manual content. /// /// Which type of content to load. protected override void LoadGraphicsContent(bool loadAllContent) { if (loadAllContent) { mSpriteBatch = new SpriteBatch(mGraphics.GraphicsDevice); mStandardFont = mContent.Load(@"Content\Fonts\Arial"); mStandardFontBig = mContent.Load(@"Content\Fonts\BigArial"); mDialogTextFont = mContent.Load(@"Content\Fonts\Nina"); mDialogLabelFont = mContent.Load(@"Content\Fonts\NinaBold"); mSpaceFontBig = mContent.Load(@"Content\Fonts\BigKimberly"); mSpaceFont = mContent.Load(@"Content\Fonts\Kimberly"); mInGameFontTiny = mContent.Load(@"Content\Fonts\TinyMakisupa"); } } /// /// Unload your graphics content. If unloadAllContent is true, you should /// unload content from both ResourceManagementMode pools. Otherwise, just /// unload ResourceManagementMode.Manual content. Manual content will get /// Disposed by the GraphicsDevice during a Reset. /// /// Which type of content to unload. protected override void UnloadGraphicsContent(bool unloadAllContent) { if (unloadAllContent == true) { mContent.Unload(); } } /// /// Interprets the lua file, pausing in the lua file's execution as necessary (for example, after a Wait(...) command). /// /// Path to the Lua script public void InterpretLuaFile(string scriptFile) { InterpretLuaFileHelper(scriptFile, true); } /// /// Interprets the lua file in the same manner as InterpretLuaFile, except commands are placed before any commands currently in the /// ScriptManager's command queue. /// /// public void InterpretLuaFileImmediately(string scriptFile) { InterpretLuaFileHelper(scriptFile, false); } /// /// Helper for InterpretLuaFile[Immediately] /// /// /// private void InterpretLuaFileHelper(string scriptFile, bool insertAtEnd) { if (scriptFile.Substring(scriptFile.Length - 4, 4).ToLower() != ".lua") scriptFile += ".lua"; // Parse the script by commands System.Diagnostics.Debug.Assert(File.Exists(scriptFile), "Script " + scriptFile + " does not exist."); StreamReader sr = File.OpenText(scriptFile); string nextLine = sr.ReadLine(); string entireFile = ""; // Ignore lines that are commented out while (nextLine != null) { if (!nextLine.Trim().StartsWith("--")) { entireFile += nextLine; } nextLine = sr.ReadLine(); } // Separate each Lua command string[] commands = entireFile.Split(';'); if (insertAtEnd) { // Enqueue each command to the script manager for (int i = 0; i < commands.Length; i++) ScriptManager.Enqueue(commands[i].Trim()); } else { // Add each command at the top of the script manager's queue for (int i = commands.Length - 1; i >= 0; i--) ScriptManager.Insert(commands[i].Trim()); } } /// /// Feeds the given command to the Lua Virtual Machine /// /// public void ExecuteLuaCommand(string luaCommand) { mLuaGameSettingsVM.DoString(luaCommand); } /// /// Changes both width and height of the screen simulatneously. /// /// In Pixels /// In Pixels [LuaCallback("SetResolution")] public void SetResolution(int newWidth, int newHeight) { mScreenWidth = newWidth; mScreenHeight = newHeight; mGraphics.PreferredBackBufferWidth = ScreenWidth; mGraphics.PreferredBackBufferHeight = ScreenHeight; mGraphics.ApplyChanges(); } /// /// True for fullscreen, false for windowed /// /// [LuaCallback("SetFullScreen")] public void SetFullScreen(bool value) { mGraphics.IsFullScreen = value; mGraphics.ApplyChanges(); } /// /// Displays a message in a standard windows form and terminates the entire program /// /// Message to display public void PrintAndDie(string message) { System.Windows.Forms.MessageBox.Show(message, "Fatal Error", System.Windows.Forms.MessageBoxButtons.OK, System.Windows.Forms.MessageBoxIcon.Error); this.Exit(); } /// /// Processes and records input (and if enabled, parallel port output) every tick, /// but only updates game logic at the rate of LogicUpdateTargetTime /// /// Provides a snapshot of timing values. protected override void Update(GameTime gameTime) { mLastGameTimeSnapshot = gameTime; mInputDT = (float)gameTime.ElapsedGameTime.TotalMilliseconds; mInputDTs.AddLast(mInputDT); mUpdateTT = (float)gameTime.TotalGameTime.TotalMilliseconds; mLogicTimer -= mInputDT; mLogicUpdate = false; InputState.Update(); OutputState.Update(mGraphics.GraphicsDevice); // Decide whether this is just an input update or a full-fledged logic update if (mLogicTimer < 0) { base.Update(gameTime); mLogicTimer += mLogicWaitTime; mLogicDT = mUpdateTT - mLastLogicTime; mLogicDTs.AddLast(mLogicDT); mLastLogicTime = mUpdateTT; mLogicUpdate = true; InputState.TakeSnapshot(); ScriptManager.Update(); Console.Update(); Pina.Update(ElapsedSeconds); Ticker.Update(); HUD.Update(); SoundManager.Update(); } } /// /// Draw generic information on top of the current game /// /// Provides a snapshot of timing values. protected override void Draw(GameTime gameTime) { float drawDT = (float)gameTime.ElapsedGameTime.TotalMilliseconds; ScriptManager.Draw(); Pina.RenderToScreen(false); Ticker.Draw(); HUD.Draw(); // Debug timepass information if (ShowTimePass) { // Average debug information over several cycles to make debug timepass information readable mDrawDTs.AddLast(drawDT); while (mInputDTs.Count > mTimePassSamples) mInputDTs.RemoveFirst(); while (mLogicDTs.Count > mTimePassSamples) mLogicDTs.RemoveFirst(); while (mDrawDTs.Count > mTimePassSamples) mDrawDTs.RemoveFirst(); // Get averaged values for readability float avgDraw = 0f; float avgLogic = 0f; float avgInput = 0f; foreach (float node in mInputDTs) avgInput += node; foreach (float node in mLogicDTs) avgLogic += node; foreach (float node in mDrawDTs) avgDraw += node; avgInput /= mTimePassSamples; avgLogic /= mTimePassSamples; avgDraw /= mTimePassSamples; mSpriteBatch.Begin(SpriteBlendMode.None, SpriteSortMode.Immediate, SaveStateMode.None); this.mStandardFont.DrawString(new Vector2(10.0F, 10.0F), "Input dt", Color.Azure); this.mStandardFont.DrawString(new Vector2(10.0F, 22.0F), String.Format("{0:0.0}", avgInput), Color.Bisque); this.mStandardFont.DrawString(new Vector2(70.0F, 10.0F), "Logic dt", Color.Azure); this.mStandardFont.DrawString(new Vector2(70.0F, 22.0F), String.Format("{0:0.0}", avgLogic), Color.Bisque); this.mStandardFont.DrawString(new Vector2(130.0F, 10.0F), "Draw dt", Color.Azure); this.mStandardFont.DrawString(new Vector2(130.0F, 22.0F), String.Format("{0:0.0}", avgDraw), Color.Bisque); this.mStandardFont.DrawString(new Vector2(200.0F, 10.0F), "Particles", Color.Aquamarine); this.mStandardFont.DrawString(new Vector2(200.0F, 22.0F), Sparx.ParticleCount + "", Color.CadetBlue); mSpriteBatch.End(); } // Script console (hides itself as apporopriate) Console.Draw(); // Let the XNA do anything else it needs to do base.Draw(gameTime); } /// /// Loads values for all tweakable member variables from the standard config file or, if present, the lab config file /// private void LoadConfigFile() { // Read in the appropriate config file string labConfigFile = "conlab.tac"; string standardConfigFile = "constd.tac"; string fileName = standardConfigFile; // Look for the lab config file if (File.Exists(labConfigFile)) { mIsLab = true; fileName = labConfigFile; } // Ensure that the config file exists if (!File.Exists(fileName)) { PrintAndDie("Could not find file " + fileName + ". Try reinstalling this program."); return; } // Decrypte the config StreamReader sr = DecryptFile(fileName); string entireFile = sr.ReadToEnd(); // Separate each Lua command by semicolons // (This is not required by Lua syntax, but it allows the script to send commands one at a time to the interprter, // allowing it to wait in between commands) string[] commands = entireFile.Split(';'); // Enqueue each command to the script manager for (int i = 0; i < commands.Length; i++) mLuaGameSettingsVM.DoString(commands[i].Trim()); } /// /// Decrypts the given config file assuming that tAC_Encrypt.exe encrypted it. This is done solely to hide the config file from the user. /// /// Encrypted File /// The ascii-formatted decrypted contents of the input file private static StreamReader DecryptFile(string inputFilename) { // This function is based on the Microsoft tutorial http://support.microsoft.com/kb/307010 string key = "theautis"; // This is the same key used in tAC_Encrypt.exe DESCryptoServiceProvider DES = new DESCryptoServiceProvider(); //A 64 bit key and IV is required for this provider. //Set secret key For DES algorithm. DES.Key = ASCIIEncoding.ASCII.GetBytes(key); //Set initialization vector. DES.IV = ASCIIEncoding.ASCII.GetBytes(key); //Create a file stream to read the encrypted file back. FileStream fsread = new FileStream(inputFilename, FileMode.Open, FileAccess.Read); //Create a DES decryptor from the DES instance. ICryptoTransform desdecrypt = DES.CreateDecryptor(); //Create crypto stream set to read and do a //DES decryption transform on incoming bytes. CryptoStream cryptostreamDecr = new CryptoStream(fsread, desdecrypt, CryptoStreamMode.Read); return new StreamReader(cryptostreamDecr); } } }