/*
* 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);
}
}
}