/* * LuaHelper.cs * Authors: August Zinsser * * Copyright Matthew Belmonte 2007 */ using System; using System.Diagnostics; using System.Collections; using System.Collections.Generic; using System.IO; using System.Text; using System.Reflection; 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 Nuclex.Fonts; using Pina3D.Particles; using Pina3D; using LuaInterface; namespace tAC_Engine { #region Helper Classes // // Helper classes are based on code found at http://www.gamedev.net/reference/articles/article2275.asp // /// /// Flag C# functions with this Attribute derivative to have RegisterLuaCallbacks find it and register it with the LuaVM /// public class LuaCallback : Attribute { private String mFunctionName; private String[] mFunctionParameters = null; /// /// Constructor /// /// /// public LuaCallback(String funcName, params String[] funcParams) { mFunctionName = funcName; mFunctionParameters = funcParams; } /// /// Constructor /// /// public LuaCallback(String funcName) { mFunctionName = funcName; } /// /// Returns the Lua name of the function /// /// public String GetFunctionName() { return mFunctionName; } /// /// Returns the parameters /// /// public String[] GetFunctionParameters() { return mFunctionParameters; } } /// /// Necessary? /// public class LuaFuncDescriptor { private String FunctionName; private String FunctionDoc; private ArrayList FunctionParameters; private ArrayList FunctionParamDocs; private String FunctionDocString; public LuaFuncDescriptor(String strFuncName, String strFuncDoc, ArrayList strParams, ArrayList strParamDocs) { FunctionName = strFuncName; FunctionDoc = strFuncDoc; FunctionParameters = strParams; FunctionParamDocs = strParamDocs; String strFuncHeader = strFuncName + "(%params%) - " + strFuncDoc; String strFuncBody = "\n\n"; String strFuncParams = ""; Boolean bFirst = true; for (int i = 0; i < strParams.Count; i++) { if (!bFirst) strFuncParams += ", "; strFuncParams += strParams[i]; strFuncBody += "\t" + strParams[i] + "\t\t" + strParamDocs[i] + "\n"; bFirst = false; } strFuncBody = strFuncBody.Substring(0, strFuncBody.Length - 1); if (bFirst) strFuncBody = strFuncBody.Substring(0, strFuncBody.Length - 1); FunctionDocString = strFuncHeader.Replace("%params%", strFuncParams) + strFuncBody; } public String getFuncName() { return FunctionName; } public String getFuncDoc() { return FunctionDoc; } public ArrayList getFuncParams() { return FunctionParameters; } public ArrayList getFuncParamDocs() { return FunctionParamDocs; } public String getFuncHeader() { if (FunctionDocString.IndexOf("\n") == -1) return FunctionDocString; return FunctionDocString.Substring(0, FunctionDocString.IndexOf("\n")); } public String getFuncFullDoc() { return FunctionDocString; } } #endregion /// /// Provides the links necessary to use C# functions in Lua scripts /// public static class LuaHelper { /// /// An instantiable class whose sole purpose is to register Lua functions when the static class /// GenericLuaHelper tells it to do so. /// private class LuaFunctionWrapper { /// /// Registers this class's functions /// public LuaFunctionWrapper() { LuaHelper.RegisterLuaCallbacks(this); } /* * Accessors for generic global variables */ [LuaCallback("SetShowTimePass")] public static void SetShowTimePass(bool value) { GenericBaseApplication.GameManager.ShowTimePass = value; } [LuaCallback("SetShowCursor")] public static void SetShowCursor(bool value) { HUD.ShowMouseCursor = value; } [LuaCallback("GetScreenWidth")] public static int GetScreenWidth() { return GenericBaseApplication.GameManager.ScreenWidth; } [LuaCallback("GetScreenHeight")] public static int GetScreenHeight() { return GenericBaseApplication.GameManager.ScreenHeight; } [LuaCallback("ScreenWidth")] public static int GetScreenWidth(float percentage) { return (int)(percentage * GenericBaseApplication.GameManager.ScreenWidth); } [LuaCallback("ScreenHeight")] public static int GetScreenHeight(float percentage) { return (int)(percentage * GenericBaseApplication.GameManager.ScreenHeight); } [LuaCallback("SetScreenDimensions")] public static void SetScreenPhysicaDimensions(float physicalWidth, float physicalHeight, float physicalDistance) { GenericBaseApplication.GameManager.ScreenPhysicalWidth = physicalWidth; GenericBaseApplication.GameManager.ScreenPhysicalHeight = physicalHeight; GenericBaseApplication.GameManager.ScreenPhysicalDistance = physicalDistance; } [LuaCallback("SetMuteAll")] public static void SetMuteAll(bool value) { SoundManager.MuteAll = value; } [LuaCallback("SetMuteSFX")] public static void SetMuteSoundEffects(bool value) { SoundManager.MuteSoundEffects = value; } [LuaCallback("SetMuteMusic")] public static void SetMuteMusic(bool value) { SoundManager.MuteMusic = value; } /* * Datatype wrappers */ [LuaCallback("Color")] public static Color Color(byte r, byte g, byte b) { return new Color(r, g, b); } [LuaCallback("Vector2")] public static Vector2 Vector2(float x, float y) { return new Vector2(x, y); } [LuaCallback("Vector3")] public static Vector3 Vector3(float x, float y, float z) { return new Vector3(x, y, z); } [LuaCallback("Vector4")] public static Vector4 Vector4(float x, float y, float z, float w) { return new Vector4(x, y, z, w); } /* * Generic script functions */ /// /// Quits immediately. Skips the shut down procedure so should only be used for debugging purposes. /// [LuaCallback("Quit")] public static void Quit() { //ScriptManager.Enqueue(new ScriptEvent("Quit", null, 0f)); GenericBaseApplication.GameManager.Exit(); } /// /// Interprets the given lua file assuming it ends in ".lua" /// /// The name of the script file with no extension [LuaCallback("RunScript")] public static void RunScript(string scriptPath) { GenericBaseApplication.GameManager.InterpretLuaFileImmediately(scriptPath); } /// /// Returns true if the given script exists (assumes file ends in ".lua") /// /// /// The name of the script file with no extension [LuaCallback("ScriptExists")] public static bool ScriptExists(string scriptPath) { return File.Exists(scriptPath) || File.Exists(scriptPath + ".lua"); } /// /// Creates a new script or erases an existing script (assumes file ends in ".lua") /// /// /// The name of the script file with no extension [LuaCallback("CreateScript")] public static void CreateScript(string scriptPath) { File.Create(scriptPath); } /// /// Inserts a pause before executing the next line in a Lua file /// /// [LuaCallback("Wait")] public static void Wait(float seconds) { // Tell the Lua interpreter to sleep ScriptManager.Sleep(seconds); } /// /// Causes the LuaVM to sleep until it recieves the specified code /// [LuaCallback("WaitFor")] public static void WaitFor(string code) { ScriptManager.Sleep(code); } /// /// Tells the Game Manager to stop updating the main game and minigames. Useful for cutscenes and tutorials. /// [LuaCallback("PauseGames")] public static void PauseGames() { GenericBaseApplication.GameManager.PauseGames = true; } /// /// Tells the Game Manager to update the main game and minigames. Cutscenes should always call this sometime after calling PauseGames(). /// [LuaCallback("ResumeGames")] public static void ResumeGames() { GenericBaseApplication.GameManager.PauseGames = false; } /// /// Tells the HUD to show a dialog with animated portrait /// [LuaCallback("ShowAnimatedDialog")] public static void ShowDialog(string label, string text, Animation animation, string clip, string sound, string position) { HUD.ShowDialog(label, text, animation, clip, sound, GetPosition(position), 0, true); } /// /// Tells the HUD to show a dialog that persists until the user presses next /// [LuaCallback("ShowStaticDialog")] public static void ShowDialog(string label, string text, string imagePath, string soundPath, string position) { Texture2D texture = null; if (imagePath != "" && imagePath != null) texture = TextureManager.Load(imagePath); HUD.ShowDialog(label, text, texture, soundPath, GetPosition(position), 0, true); } /// /// Tells the HUD to show a dialog that can only disappear through code /// [LuaCallback("ShowPersistentStaticDialog")] public static void ShowNonSkippableDialog(string label, string text, string imagePath, string soundPath, string position) { Texture2D texture = null; if (imagePath != "" && imagePath != null) texture = TextureManager.Load(imagePath); HUD.ShowDialog(label, text, texture, soundPath, GetPosition(position), 0, false); } /// /// Tells the HUD to show a dialog that persists for the specified interval /// [LuaCallback("ShowTimedStaticDialog")] public static void ShowDialog(string label, string text, string imagePath, string soundPath, string position, float seconds) { Texture2D texture = null; if (imagePath != "" && imagePath != null) texture = TextureManager.Load(imagePath); HUD.ShowDialog(label, text, texture, soundPath, GetPosition(position), seconds, false); } /// /// Clears all dialog boxes from the HUD /// [LuaCallback("ClearDialogs")] public static void ClearDialogs() { HUD.ClearDialogs(); } /// /// Plays the given sound effect /// /// [LuaCallback("PlaySoundEffect")] public static void PlaySoundEffect(string name) { SoundManager.PlayEffect(name); } /// /// Sets the background music to the track specified /// /// [LuaCallback("SetMusic")] public static void SetMusic(string name) { SoundManager.SetMusic(name); } /// /// Stops the current music track /// [LuaCallback("StopMusic")] public static void StopMusic() { SoundManager.StopMusic(); } /// /// Pauses the current music track /// [LuaCallback("PauseMusic")] public static void PauseMusic() { SoundManager.PauseMusic(); } /// /// Unpauses the current music track /// [LuaCallback("ResumeMusic")] public static void ResumeMusic() { SoundManager.ResumeMusic(); } /// /// "High" "Medium" or "Low" /// /// [LuaCallback("SetTextureLOD")] public static void SetTextureLevelOfDetail(string lod) { if (lod.ToLower() == "high" || lod.ToLower() == "hi") TextureManager.LOD = TextureManager.LevelOfDetail.High; else if (lod.ToLower() == "medium" || lod.ToLower() == "med") TextureManager.LOD = TextureManager.LevelOfDetail.Medium; else if (lod.ToLower() == "low" || lod.ToLower() == "lo") TextureManager.LOD = TextureManager.LevelOfDetail.Low; else Debug.Assert(false, "Unreckognized texture level of detail: " + lod + "\nSpecify \"High\" \"Medium\" or \"Low\""); } /// /// Clears the heads-up-display /// [LuaCallback("ClearHUD")] public static void ClearHUD() { HUD.Clear(); } /// /// Wrapper for Ticker.Display(...) /// [LuaCallback("DisplayText")] public static void DisplayText(string text, float offsetFromCenterX, float offsetFromCenterY, float velocityX, float velocityY, string font, byte colorR, byte colorG, byte colorB, byte colorA, float seconds, bool fade) { // Get the appropriate font Ticker.Font tickerFont = Ticker.Font.Standard; if (font == "Space_Big") tickerFont = Ticker.Font.Space_Big; if (font == "Standard_Big") tickerFont = Ticker.Font.Standard_Big; // Get the appropriate color Color color = new Color(colorR, colorG, colorB); float opacity = (float)colorA / 255f; Ticker.Display(text, null, Microsoft.Xna.Framework.Vector2.Zero, new Vector2(offsetFromCenterX, offsetFromCenterY), new Vector2(velocityX, velocityY), tickerFont, color, opacity, seconds, fade); } /// /// Loads the specified texture at the appropriate resolution (based on user settings) /// [LuaCallback("LoadTexture")] public static Texture2D LoadTexture(string texturePath) { return TextureManager.Load(texturePath); } ///// ///// Loads the specified animation at the appropriate resolution (based on user settings) ///// //[LuaCallback("CreateAnimation")] //public static Texture2D CreateAnimation(string texturePath, int frames, int frameOffset) //{ // // TOOD: Change resolution tag as appropriate // string resTag = "_Hi"; //} /// /// Creates an entity with the given texture /// /// [LuaCallback("CreateEntity")] public static Entity CreateEntity(Texture2D texture) { Entity entity = new Entity(texture); ScriptManager.AddEntity(entity); return entity; } /// /// Removes the Entity from the ScriptManager. Note that the data cannot be garbage collected until the LuaVM lets go of it. /// /// [LuaCallback("DestroyEntity")] public static void DestroyEntity(Entity entity) { ScriptManager.RemoveEntity(entity); } /// /// Removes all entities from the ScriptManager /// [LuaCallback("FlushEntities")] public static void FlushEntities() { ScriptManager.FlushEntities(); } /// /// Loads and immediately runs a new particle effect at the specified location /// /// /// [LuaCallback("LoadParticleEffect")] public static Emitter LoadParticleEffect(string particleEffectPath, float x, float y, float z) { Emitter emitter = Sparx.LoadParticleEffect(particleEffectPath); emitter.Position3D = new Vector3(x, y, z); Sparx.AddEmitter(emitter); return emitter; } /// /// Flushes all particles in the particle engine /// [LuaCallback("FlushParticles")] public static void FlushParticles() { Sparx.Flush(); } /// /// Loads a ColladaFile from a .dae asset. This data is then passed on to instances of PinaModel. /// /// /// [LuaCallback("LoadColladaSource")] public static ColladaFile LoadColladaSource(string assetPath) { ColladaFile sourceData = new ColladaFile(assetPath); return sourceData; } /// /// Removes the ColladaModel from the render queue. Note that the data cannot be garbage collected until the LuaVM lets go of it. /// /// [LuaCallback("DestroyModel")] public static void DestroyModel(PinaModel model) { Pina.RemoveRenderable(model); } /// /// Creates an instanced renderable based on the given model and adds it to the RenderQueue /// [LuaCallback("CreateModel")] public static PinaModel CreateModel(ColladaFile sourceData) { PinaModel model = new PinaModel(new PinaState(Matrix.Identity), sourceData); Pina.AddRenderable(model); return model; } /// /// Creates a new camera that can be used to look at a Pina scene /// /// [LuaCallback("CreateCamera")] public static Camera CreateCamera() { Camera camera = new Camera(); return camera; } /// /// Sets Pina's current camera to this one /// /// [LuaCallback("LookThroughCamera")] public static void LookThroughCamera(Camera camera) { Pina.Camera = camera; } /// /// Sets Pina's directional light direction /// [LuaCallback("SetLightDir")] public static void SetSceneLightDirection(float x, float y, float z) { Pina.GlobalLightDirection = new Vector3(x, y, z); } /// /// Sets Pina's directional light power /// [LuaCallback("SetLightPower")] public static void SetSceneLightPower(float p) { Pina.GlobalLightPower = p; } /// /// Sets Pina's ambient light power /// [LuaCallback("SetLightAmbient")] public static void SetSceneLightAmbientPower(float p) { Pina.GlobalLightAmbient = p; } /// /// Sets Pina's ambient light color /// [LuaCallback("SetLightColor")] public static void SetSceneLightColor(Color color) { Pina.GlobalLightColor = color; } /// /// Creates a new phase queue or overwrites an existing phase queue for a specific minigame. /// Invalid data may not be caught until that minigame attempts to use it. /// /// Name of the minigame /// The number of parameters for the specific minigame's test constructor (usually 2; phaseName, trials) /// A comma-seperated list of each phase parameter in order. ie: phaseParams = "PhaseTypeA, 1, PhaseTypeB, 5, PhaseTypeA, 2, ... [LuaCallback("SetPhaseQueue")] public static void SetPhaseQueue(string gameName, int phaseParamStride, string phaseParams) { // Split the phaseParams into an array string[] paramArray = phaseParams.Split(','); // Generate a new phase queue GenericPhaseQueue phaseQueue = new GenericPhaseQueue(); // Do some basic validation on the phase queue if (paramArray.Length < phaseParamStride) GenericBaseApplication.GameManager.PrintAndDie("Malformed phase queue in config file for game: " + gameName + ". Phase queue must contain at least 1 test phase."); if (paramArray.Length % phaseParamStride != 0) GenericBaseApplication.GameManager.PrintAndDie("Malformed phase queue in config file for game: " + gameName + ". Phase parameters do not agree with phase parameter stride."); // Generate each test and then add it to the phaseQueue for (int i = 0; i < paramArray.Length; i += phaseParamStride) { // Get name and trials GenericTest test = new GenericTest(paramArray[i].Trim(), int.Parse(paramArray[i + 1].Trim()), null); // Get remaining params for (int j = 2; j < phaseParamStride; j++) test.SpecificParameters.Add(paramArray[j].Trim()); phaseQueue.Enqueue(test); } GenericBaseApplication.GameManager.SetPhaseQueueForMiniGame(gameName, phaseQueue); } /// /// Converts the string position to the enum Dialog.Position /// /// /// private static Dialog.Position GetPosition(string position) { if (position.ToLower() == "bottomleft") return Dialog.Position.BottomLeft; else if (position.ToLower() == "bottomright") return Dialog.Position.BottomRight; else if (position.ToLower() == "topleft") return Dialog.Position.TopLeft; else if (position.ToLower() == "topright") return Dialog.Position.TopRight; else throw new Exception("Position Parameter is not a valid Dialog.Position"); } } static LuaFunctionWrapper mFuncWrapper; // Instance member with the ability to register its functions /// /// Registers this class's functions with the Lua VM. Assumes the Lua VM already exists. /// static LuaHelper() { // Since static classes can't register their functions the way the function registration process // works, declare an instance member who can. mFuncWrapper = new LuaFunctionWrapper(); } /// /// Call this from any class to register any of that class's functions that contain a LuaCallback attribute. /// Based on code found at http://www.gamedev.net/reference/articles/article2275.asp /// /// A reference to the class to look for LuaCallback annotations public static void RegisterLuaCallbacks(Object cSharpClass) { // Don't check if the LuaVM has been created so an exception gets thrown // Get the C# type Type cSharpType = cSharpClass.GetType(); // Search each function reflection for a LuaCallback foreach (MethodInfo info in cSharpType.GetMethods()) { foreach (Attribute attribute in Attribute.GetCustomAttributes(info)) { if (attribute.GetType() == typeof(LuaCallback)) { // Found one LuaCallback luaCallback = (LuaCallback)attribute; // Parse the attribute string funcName = luaCallback.GetFunctionName(); string[] funcParams = luaCallback.GetFunctionParameters(); // Get the C# parameter info ParameterInfo[] paramInfo = info.GetParameters(); // Make sure the LuaCallback params match the C# params if (funcParams != null && (funcParams.Length != paramInfo.Length)) { System.Diagnostics.Debug.Assert(false, "The function " + funcName + " has an LuaCallback attribute whose parameters do not match the C# function."); // Ignore it and move on break; } // Register the function with the Lua VM GenericBaseApplication.GameManager.LuaGameManagerVM.RegisterFunction(funcName, cSharpClass, info); } } } } } }