You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1411 lines
48 KiB
C#

using SFML.Window;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Reflection;
using System.Text;
namespace Otter {
/// <summary>
/// The debug console. Only exists when the game is built in Debug Mode. The game will handle
/// using this class. Can be summoned by default with the ~ key.
/// </summary>
public class Debugger {
#region Private Fields
Text textInput = new Text(24);
Text textInputHelp = new Text(24);
Text textCamera = new Text("Move camera with arrow keys, F2 to exit.", 24);
Text textPastCommands = new Text(16);
Text textCommandsBuffered = new Text(12);
Text textCountdown = new Text(50);
Text textFramesLeft = new Text(24);
Text textPerformance = new Text(24);
Text textPastCommandsLive = new Text(16);
List<string> logTags = new List<string>() { "", "ERROR" };
Image imgScrollBarBg;
Image imgScrollBar;
Image imgOtter;
Image imgOverlay;
Image imgError;
int mouseScrollSpeed = 1;
int textSizeSmall = 12,
textSizeMedium = 16,
textSizeLarge = 24,
textSizeHuge = 50;
string keyString = "";
string tabbedString = "";
int tabbedIndex = 0;
int liveConsoleLines = 0;
bool enterPressed;
bool dismissPressed;
bool executionError;
bool locked;
bool autoSummon;
int paddingMax = 30;
int padding = 30;
int maxLines;
int scrollBarWidth = 10;
int textAreaHeight;
int maxChars = 15;
float time;
float x, y;
Dictionary<string, MethodInfo> commands = new Dictionary<string, MethodInfo>();
Dictionary<Type, object> typeInstances = new Dictionary<Type, object>();
HashSet<string> enabledGroups = new HashSet<string>();
List<string> commandBuffer = new List<string>();
List<string> debugLog = new List<string>();
Dictionary<string, object> watching = new Dictionary<string, object>();
int debugLogBufferSize = 10000;
int logIndex;
float countDownTimer;
int advanceFrames;
List<string> inputHistory = new List<string>();
int inputHistoryIndex;
bool toggleKeyPressed;
Surface renderSurface;
float backgroundAlpha = 0.6f;
int dismissFor;
int showPerformance;
int currentState;
bool cameraTogglePressed = false;
int stateNormal;
int stateCamera = 1;
int cameraMoveRate = 1;
bool commandInputEnabled = true;
int debugCamX;
int debugCamY;
#endregion
#region Public Fields
/// <summary>
/// Reference to the active instance of the debugger.
/// </summary>
public static Debugger Instance;
/// <summary>
/// The key used to summon and dismiss the debug console.
/// </summary>
public Key ToggleKey = Key.Tilde;
#endregion
#region Public Properties
/// <summary>
/// If the debug console is currently open.
/// </summary>
public bool IsOpen { get; private set; }
/// <summary>
/// If the debug console is currently visible.
/// </summary>
public bool Visible { get; private set; }
/// <summary>
/// The offset of the camera X set by debug camera mode.
/// </summary>
public float DebugCameraX { get; private set; }
/// <summary>
/// The offset of the camera Y set by debug camera mode.
/// </summary>
public float DebugCameraY { get; private set; }
/// <summary>
/// The size of the live console in lines. If 0 the live console is hidden.
/// </summary>
public int LiveConsoleSize {
get {
return liveConsoleLines;
}
set {
liveConsoleLines = value;
liveConsoleLines = (int)Util.Clamp(liveConsoleLines, 0, maxLines + 3);
}
}
#endregion
#region Private Methods
#region Default Commands
[OtterCommand(alias: "help", helpText: "Shows help.")]
void CmdHelp() {
Log("", false);
var cmds = GetActiveCommands();
var maxCommandNameLength = cmds.Max(c => c.Key.Length);
var maxGroupNameLength = cmds.Max(c => GetOtterCommand(c.Key).Group);
Log("== Available Commands:", false);
cmds // this looks so stupid lol
//.OrderBy(kv => kv.Key)
.GroupBy(kv => GetOtterCommand(kv.Key).Group)
.OrderBy(kv => kv.Key)
.Each(group => {
if (group.Key != "") {
Log("", false);
Log(string.Format("= {0}", group.Key), false);
}
group.Each(cmd => {
var attr = GetOtterCommand(cmd.Value);
var s = cmd.Key;
if (attr.HelpText != "") {
s = s.PadRight(maxCommandNameLength + 2, ' ');
s += ": ";
s += attr.HelpText;
}
Log(s, false);
});
});
Log("", false);
Log("== Other:", false);
Log("Press F2 to move the camera.", false);
Log("", false);
Log("== End of Help.", false);
Log("", false);
}
[OtterCommand(
alias: "music",
helpText: "Change the music volume. 0 to 1."
)]
void CmdMusic(float volume) {
Music.GlobalVolume = volume;
}
[OtterCommand(
alias: "sound",
helpText: "Change the sound volume. 0 to 1."
)]
void CmdSound(float volume) {
Sound.GlobalVolume = volume;
}
[OtterCommand(
alias: "overlay",
helpText: "Set the opacity of the console background. 0 to 1."
)]
void CmdOverlay(float amount) {
backgroundAlpha = Util.Clamp(amount, 0, 1);
}
[OtterCommand(
alias: "exit",
helpText: "Exits the game."
)]
void CmdExit() {
game.Close();
}
[OtterCommand(
alias: "clear",
helpText: "Clears the console."
)]
void CmdClear() {
inputHistory.Clear();
inputHistoryIndex = 0;
debugLog.Clear();
logIndex = 0;
Log("Log cleared.");
}
[OtterCommand(
alias: "showfps",
helpText: "Shows performance information. 0 to 5."
)]
void CmdFps(int level) {
Console.WriteLine(level);
ShowPerformance(level);
}
[OtterCommand(
alias: "next",
helpText: "Advances the game by a set number of updates."
)]
void CmdNext(int advanceFrames) {
if (game.MeasureTimeInFrames && game.FixedFramerate) {
countDownTimer = 30;
}
else {
countDownTimer = 0.5f;
}
this.advanceFrames = (int)Util.Max(advanceFrames, 1);
locked = true;
}
[OtterCommand(
alias: "spawn",
helpText: "Add a new entity at a set position.",
usageText: "Add a new entity to the Scene at x, y.\nThe entityName must be an entity type,\nand must have a constructor that accepts\ntwo floats or two ints."
)]
void CmdSpawn(string entityName, float x, float y) {
Type entityType = Util.GetTypeFromAllAssemblies(entityName, true);
if (entityType != null) {
object entity = null;
try {
entity = Activator.CreateInstance(entityType, x, y);
}
catch { }
if (entity == null) {
try {
entity = Activator.CreateInstance(entityType, (int)x, (int)y);
}
catch { }
}
if (entity == null) {
Error("Entity doesn't have constructor with X, Y.");
}
else {
game.Scene.Add((Entity)entity);
}
}
else {
//throw new Exception("Entity type not found."); //Exceptions don't play nice with MethodInfo
Error("Entity type not found.");
}
}
[OtterCommand(
alias: "watch",
helpText: "Display watched values."
)]
void CmdWatch() {
Log("", false);
Log("== Watching Vars", false);
foreach (var w in watching) {
Log(w.Key.PadRight(20) + w.Value.ToString(), false);
}
Log("", false);
}
[OtterCommand(
alias: "log",
helpText: "Toggle log tags."
)]
void CmdLog(string tag) {
tag = tag.ToUpper();
if (tag == "") return;
if (logTags.Contains(tag)) {
logTags.Remove(tag);
Log("Removed tag " + tag);
}
else {
logTags.Add(tag);
Log("Added tag " + tag);
}
}
[OtterCommand(
alias: "livelog",
helpText: "Displays a set number lines of the console live."
)]
void CmdLiveLog(int lines) {
LiveConsoleSize = lines;
}
#endregion
#region EventHandlers
void OnTextEntered(object sender, TextEventArgs e) {
if (locked) return;
if (!commandInputEnabled) return;
string hexValue = (Encoding.ASCII.GetBytes(e.Unicode)[0].ToString("X"));
int ascii = (int.Parse(hexValue, NumberStyles.HexNumber));
if (e.Unicode == "\t") { // Tab completion, may be totally buggy?
if (keyString != "") {
if (tabbedString == "") {
tabbedString = keyString;
}
var commandKeys = GetActiveCommands().Keys.ToList<string>();
var matches = commandKeys.Where(c => c.StartsWith(tabbedString));
if (matches.Count() > 0) {
keyString = matches.ElementAt(tabbedIndex);
tabbedIndex++;
if (tabbedIndex == matches.Count()) tabbedIndex = 0;
}
}
}
else {
tabbedString = "";
tabbedIndex = 0;
}
if (e.Unicode == "\b") {
if (keyString.Length > 0) {
keyString = keyString.Remove(keyString.Length - 1, 1);
}
}
else if (ascii >= 32 && ascii < 128) {
keyString += e.Unicode;
}
if (GetActiveCommands().ContainsKey(ParseCommandName(keyString))) {
var cmd = ParseCommandName(keyString);
var helpStr = "";
helpStr = cmd + " ";
commands[cmd].GetParameters().Each(p => {
helpStr += string.Format("({0}){1} ", ParameterTypeToString(p), p.Name);
});
textInputHelp.String = "> " + helpStr;
}
else {
textInputHelp.String = "";
}
if (keyString == "") {
textInputHelp.String = "";
}
}
void OnMouseWheel(object sender, MouseWheelEventArgs e) {
logIndex -= e.Delta * mouseScrollSpeed;
UpdateConsoleText();
}
void OnKeyPressed(object sender, KeyEventArgs e) {
if (locked) return;
//why did I make this without the input manager ugh
game.Window.SetKeyRepeatEnabled(true);
if (currentState == stateNormal) {
switch ((Key)e.Code) {
case Key.Return:
enterPressed = true;
break;
case Key.PageUp:
logIndex -= 1;
if (e.Shift) logIndex -= maxLines;
UpdateConsoleText();
break;
case Key.PageDown:
logIndex += 1;
if (e.Shift) logIndex += maxLines;
UpdateConsoleText();
break;
case Key.Up:
LoadPreviousInput();
break;
case Key.Down:
LoadNextInput();
break;
case Key.LShift:
mouseScrollSpeed = 5;
break;
case Key.RShift:
mouseScrollSpeed = 20;
break;
case Key.LAlt:
Visible = false;
break;
}
if ((Key)e.Code == ToggleKey) {
dismissPressed = true;
}
}
else if (currentState == stateCamera) {
switch ((Key)e.Code) {
case Key.Up:
debugCamY -= cameraMoveRate;
break;
case Key.Down:
debugCamY += cameraMoveRate;
break;
case Key.Left:
debugCamX -= cameraMoveRate;
break;
case Key.Right:
debugCamX += cameraMoveRate;
break;
}
}
switch ((Key)e.Code) {
case Key.F2:
cameraTogglePressed = true;
break;
}
}
void OnKeyReleased(object sender, KeyEventArgs e) {
if (locked) return;
if (currentState == stateNormal) {
switch ((Key)e.Code) {
case Key.LShift:
mouseScrollSpeed = 1;
break;
case Key.RShift:
mouseScrollSpeed = 1;
break;
case Key.LAlt:
Visible = true;
break;
}
}
}
void OnKeyPressedToggle(object sender, KeyEventArgs e) {
if ((Key)e.Code == ToggleKey) {
toggleKeyPressed = true;
}
}
#endregion
OtterCommand GetOtterCommand(MethodInfo mi) {
return (OtterCommand)mi.GetCustomAttributes(typeof(OtterCommand), false)[0];
}
OtterCommand GetOtterCommand(string commandName) {
return GetOtterCommand(commands[commandName]);
}
Dictionary<string, MethodInfo> GetActiveCommands() {
return commands.Where(c => {
var attr = GetOtterCommand(c.Key);
return enabledGroups.Contains(attr.Group) || GetOtterCommand(c.Key).Group == "";
}).ToDictionary(kv => kv.Key, kv => kv.Value);
}
void SendCommand(string str) {
enterPressed = false;
textInputHelp.String = "";
if (str == "" || str == null) return;
str = str.Trim();
Log(str);
UpdateInputHistory(str);
var cmdName = ParseCommandName(str);
if (GetActiveCommands().ContainsKey(cmdName)) {
commandBuffer.Add(str);
var attr = GetOtterCommand(cmdName);
if (!attr.IsBuffered) {
ExecuteCommand();
}
}
else {
Log("error", string.Format("Command \"{0}\" not found.", str));
ErrorFlash();
}
UpdateConsoleText();
ClearKeystring();
}
void UpdateInputHistory(string str) {
if (inputHistory.Count > 0) {
if (inputHistory[inputHistory.Count - 1] != str) {
inputHistory.Add(str);
}
}
else {
inputHistory.Add(str);
}
inputHistoryIndex = inputHistory.Count;
}
void LoadPreviousInput() {
if (inputHistory.Count == 0) return;
inputHistoryIndex -= 1;
inputHistoryIndex = (int)Util.Clamp(inputHistoryIndex, 0, inputHistory.Count - 1);
keyString = inputHistory[inputHistoryIndex];
}
void LoadNextInput() {
if (inputHistory.Count == 0) return;
inputHistoryIndex += 1;
inputHistoryIndex = (int)Util.Clamp(inputHistoryIndex, 0, inputHistory.Count - 1);
keyString = inputHistory[inputHistoryIndex];
}
string ParseCommandName(string str) {
if (str.Contains(' ')) {
return str.Split(' ')[0].ToLower();
}
return str.ToLower();
}
void ExecuteCommands() {
while (commandBuffer.Count > 0) {
ExecuteCommand(0);
}
}
void ExecuteCommand(int index = -1) {
if (index == -1) index = commandBuffer.Count - 1;
string cmd = commandBuffer[index];
//parse the string, when inside a quote replace space with something else
bool inQuote = false;
string parsedCmd = "";
for (int i = 0; i < cmd.Length; i++) {
char nextChar = cmd[i];
if (cmd[i] == '"') {
inQuote = !inQuote;
}
if (inQuote) {
if (cmd[i] == ' ') {
nextChar = (char)16;
}
}
parsedCmd += nextChar;
}
string[] split = parsedCmd.Split(' ');
string methodName = split[0].ToLower();
string[] parameters = new string[split.Length - 1];
//restore spaces
for (int i = 1; i < split.Length; i++) {
split[i] = split[i].Replace((char)16, ' ');
if (split[i][0] == '"') {
//get rid of quotes in string arguments
split[i] = split[i].Replace("\"", "");
}
parameters[i - 1] = split[i];
}
bool usageMode = false;
if (parameters.Length == 0) {
if (commands[methodName].GetParameters().Count() > 0) {
usageMode = true;
}
}
if (commands[methodName].GetParameters().Count() != parameters.Length) {
if (!usageMode) {
Log("error", "Invalid amount of parameters.");
ErrorFlash();
}
}
if (commands.ContainsKey(methodName)) {
if (usageMode) {
ShowUsage(methodName);
}
else if (commands[methodName].GetParameters().Count() == parameters.Length) {
try {
Invoke(methodName, parameters);
}
catch (Exception ex) {
Log("error", ex.Message);
if (GetOtterCommand(methodName).IsBuffered) {
executionError = true;
}
else {
ErrorFlash();
}
}
}
}
commandBuffer.RemoveAt(index);
}
string ParameterTypeToString(ParameterInfo p) {
if (p.ParameterType == typeof(int)) {
return "int";
}
if (p.ParameterType == typeof(string)) {
return "string";
}
if (p.ParameterType == typeof(float)) {
return "float";
}
if (p.ParameterType == typeof(bool)) {
return "bool";
}
return "";
}
void ShowUsage(string methodName) {
Log("", false);
Log(string.Format("== Command Details: {0}", methodName), false);
var helpStr = "";
commands[methodName].GetParameters().Each(p => {
var ptype = ParameterTypeToString(p);
helpStr += string.Format("({0}) {1}, ", ptype, p.Name);
});
helpStr = helpStr.TrimEnd(',', ' ');
Log(helpStr, false);
var usageStr = GetOtterCommand(methodName).UsageText;
if (usageStr != "") {
Log("", false);
Log(usageStr, false);
}
Log("", false);
Log(string.Format("== End of Usage Details", methodName), false);
}
void Invoke(string methodName, string[] parameters) {
var mi = commands[methodName];
object instance = null;
if (!mi.IsStatic) {
if (mi.DeclaringType == typeof(Debugger)) {
instance = this;
}
else {
instance = typeInstances[mi.DeclaringType];
}
}
try {
commands[methodName].Invoke(instance, ParseParameters(commands[methodName], parameters));
}
catch (Exception ex) {
throw ex.InnerException;
}
}
object[] ParseParameters(MethodInfo mi, string[] str) {
object[] obj = new object[str.Length];
bool success = true;
if (mi.GetParameters().Count() != str.Length) {
throw new ArgumentException("Invalid amount of parameters.");
}
mi.GetParameters().EachWithIndex((p, i) => {
var ptype = p.ParameterType;
if (ptype == typeof(float)) {
float value;
if (!float.TryParse(str[i].TrimEnd('f'), out value)) {
throw new ArgumentException(string.Format("Error parsing float for parameter {0}", i));
}
else {
obj[i] = value;
}
}
if (ptype == typeof(int)) {
int value;
if (!int.TryParse(str[i], out value)) {
throw new ArgumentException(string.Format("Error parsing int for parameter {0}", i));
}
else {
obj[i] = value;
}
}
if (ptype == typeof(bool)) {
bool value;
if (!bool.TryParse(str[i], out value)) {
throw new ArgumentException(string.Format("Error parsing bool for parameter {0}", i));
}
else {
obj[i] = value;
}
}
if (ptype == typeof(string)) {
obj[i] = str[i];
}
});
if (!success) return null;
return obj;
}
void UpdateConsoleText() {
textPastCommands.String = "";
textPastCommandsLive.String = "";
int logMax = (int)Util.Max(debugLog.Count - maxLines, 0);
logIndex = (int)Util.Clamp(logIndex, 0, logMax);
int logStart = (int)Util.Clamp(logIndex, 0, logMax);
for (var i = 0; i < maxLines; i++) {
if (i < debugLog.Count) {
textPastCommands.String += debugLog[i + logStart] + "\n";
}
}
int liveLogStart = (int)Util.Clamp(debugLog.Count - liveConsoleLines, 0, debugLog.Count);
for (var i = 0; i < liveConsoleLines; i++) {
if (i < debugLog.Count) {
textPastCommandsLive.String += debugLog[i + liveLogStart] + "\n";
}
}
if (commandBuffer.Count > 0) {
textCommandsBuffered.String = "[" + commandBuffer.Count + "] Commands to be executed. Press [" + ToggleKey + "] to execute.";
}
else {
textCommandsBuffered.String = "";
}
}
void ClearKeystring() {
keyString = "";
}
void ErrorFlash() {
imgError.Alpha = 0.5f;
}
void UpdatePerformance() {
textPerformance.String = "";
if (showPerformance == 1) {
textPerformance.String = game.Framerate.ToString("00.0") + " FPS";
}
else if (showPerformance == 2) {
textPerformance.String = game.Framerate.ToString("00.0") + " FPS " + game.AverageFramerate.ToString("00.0") + " AVG";
}
else if (showPerformance == 3) {
textPerformance.String = game.Framerate.ToString("00.0") + " FPS " + game.AverageFramerate.ToString("00.0") + " AVG";
textPerformance.String += "\nUpdate " + game.UpdateCount.ToString("0000") + " Entities";
textPerformance.String += "\nRender " + game.RenderCount.ToString("0000") + " Renders";
}
else if (showPerformance == 4) {
textPerformance.String = game.Framerate.ToString("00.0") + " FPS " + game.AverageFramerate.ToString("00.0") + " AVG " + game.RealDeltaTime.ToString("0") + "ms";
textPerformance.String += "\nUpdate " + game.UpdateTime.ToString("00") + "ms (" + game.UpdateCount.ToString("0000") + " Entities)";
textPerformance.String += "\nRender " + game.RenderTime.ToString("00") + "ms (" + game.RenderCount.ToString("0000") + " Renders)";
}
else if (showPerformance >= 5) {
textPerformance.String = game.Framerate.ToString("00.0") + " FPS " + game.AverageFramerate.ToString("00.0") + " AVG " + game.RealDeltaTime.ToString("0") + "ms " + (GC.GetTotalMemory(false) / 1024 / 1024).ToString("00") + "MB";
textPerformance.String += "\nUpdate " + game.UpdateTime.ToString("00") + "ms (" + game.UpdateCount.ToString("0000") + " Entities)";
textPerformance.String += "\nRender " + game.RenderTime.ToString("00") + "ms (" + game.RenderCount.ToString("0000") + " Renders)";
}
textPerformance.Update();
textPerformance.Y = 0;
textPerformance.X = renderSurface.Width - textPerformance.Width;
}
#endregion
#region Public Methods
/// <summary>
/// Summons the Debugger.
/// </summary>
public void Summon() {
if (IsOpen) return;
if (dismissFor > 0) return;
game.ShowDebugger = true;
game.Input.bufferReleases = false;
game.Window.SetKeyRepeatEnabled(false);
game.debuggerAdvance = 0;
imgOverlay.Alpha = 0;
imgOtter.Alpha = 0;
AddInput();
IsOpen = true;
if (autoSummon) {
Log("Next " + advanceFrames + " updates completed.");
}
else {
Log("Debugger opened.");
}
UpdateConsoleText();
autoSummon = false;
Visible = true;
}
/// <summary>
/// Display performance information at a specified detail level. Set to 0 to disable. 5 is the most detailed.
/// </summary>
/// <param name="level">The level of detail. 0 for disabled, 5 for the most detailed.</param>
public void ShowPerformance(int level) {
showPerformance = level;
}
/// <summary>
/// Toggle the logging of a specific tag. If the tag is off, it will be turned on, and vice versa.
/// </summary>
/// <param name="tag">The tag to toggle.</param>
public void LogTag(string tag) {
CmdLog(tag);
}
/// <summary>
/// Enables commands in a specific group to be used.
/// </summary>
/// <param name="group"></param>
public void EnableCommandGroup(string group) {
enabledGroups.Add(group);
}
/// <summary>
/// Disables commands in a specific group.
/// </summary>
/// <param name="group"></param>
public void DisableCommandGroup(string group) {
enabledGroups.Remove(group);
}
/// <summary>
/// Writes log data to the console.
/// </summary>
/// <param name="tag">The tag to associate the log with.</param>
/// <param name="str">The string to add to the console.</param>
/// <param name="timestamp">Include a timestamp with the item.</param>
public void Log(string tag, object str, bool timestamp = true) {
tag = tag.ToUpper();
if (str.ToString().Contains('\n')) {
var split = str.ToString().Split('\n');
foreach (var s in split) {
Log(tag, s, timestamp);
}
return;
}
if (logIndex == debugLog.Count - maxLines) {
logIndex++;
}
if (debugLog.Count == debugLogBufferSize) {
debugLog.RemoveAt(0);
}
var tagstr = "";
if (tag != "") {
tagstr = string.Format("[{0}] ", tag);
str = tagstr + str;
}
if (timestamp) {
string format = game.MeasureTimeInFrames && game.FixedFramerate ? "000000" : "00000.000";
str = game.Timer.ToString(format) + ": " + str;
}
if (logTags.Contains(tag.ToUpper())) {
debugLog.Add(str.ToString());
UpdateConsoleText();
}
}
/// <summary>
/// Writes log data to the console.
/// </summary>
/// <param name="str">The string to add to the console.</param>
/// <param name="timestamp">Include a timestamp with the item.</param>
public void Log(object str, bool timestamp = true) {
Log("", str, timestamp);
}
/// <summary>
/// Send an error message to the debugger. Only really makes sense when the debugger is currently open,
/// so probably want to call this from an OtterCommand method when something goes wrong.
/// </summary>
/// <param name="message">The message to show.</param>
public void Error(string message) {
Log("ERROR", message);
ErrorFlash();
}
/// <summary>
/// Add a variable to the watch list of the debug console. This must be called on every update
/// to see the latest value!
/// </summary>
/// <param name="str">The label for the value.</param>
/// <param name="obj">The value.</param>
public void Watch(string str, object obj) {
if (watching.ContainsKey(str)) {
watching.Remove(str);
}
watching.Add(str, obj);
}
/// <summary>
/// Refreshes the available commands by finding any methods tagged with the OtterCommand attribute.
/// Don't do this a lot.
/// </summary>
public void RegisterCommands() {
commands.Clear();
typeInstances.Clear();
AppDomain.CurrentDomain.GetAssemblies()
.Each(a => a.GetTypes()
.Each(t => t.GetMethods(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static)
.Each(m => {
if (m.IsDefined(typeof(OtterCommand), false)) {
var key = m.Name.ToLower();
var attr = GetOtterCommand(m);
if (attr.Alias != "") {
key = attr.Alias.ToLower();
}
if (attr.Group != "") {
enabledGroups.Add(attr.Group);
}
commands.Add(key, m);
if (!m.IsStatic) {
if (m.DeclaringType != typeof(Debugger)) {
if (!typeInstances.ContainsKey(m.DeclaringType)) {
typeInstances.Add(
m.DeclaringType,
Activator.CreateInstance(m.DeclaringType, null)
);
}
}
}
}
})));
}
#endregion
#region Internal
internal Debugger(Game game) {
Instance = this;
this.game = game;
imgOtter = new Image(new Texture(System.Reflection.Assembly.GetExecutingAssembly().GetManifestResourceStream("Otter.otterlogo.png")));
imgOtter.Batchable = false;
imgOtter.CenterOrigin();
imgOtter.Scroll = 0;
UpdateSurface();
textInput.Scroll = 0;
textInput.OutlineThickness = 2;
textInput.OutlineColor = Color.Black;
textInputHelp.Scroll = 0;
textInputHelp.OutlineThickness = 2;
textInputHelp.Color = Color.Shade(0.67f);
textInputHelp.OutlineColor = Color.Black;
textPastCommands.Scroll = 0;
textPastCommands.OutlineColor = Color.Black;
textPastCommands.OutlineThickness = 1;
textPastCommandsLive.Scroll = 0;
textPastCommandsLive.OutlineColor = Color.Black;
textPastCommandsLive.OutlineThickness = 2;
textCommandsBuffered.Scroll = 0;
textCommandsBuffered.OutlineThickness = 2;
textCommandsBuffered.OutlineColor = Color.Black;
textCommandsBuffered.Color = Color.Gold;
textCountdown.Scroll = 0;
textCountdown.OutlineThickness = 3;
textCountdown.OutlineColor = Color.Black;
textFramesLeft.Scroll = 0;
textFramesLeft.OutlineThickness = 2;
textFramesLeft.OutlineColor = Color.Black;
textPerformance.Scroll = 0;
textPerformance.OutlineColor = Color.Black;
textPerformance.OutlineThickness = 2;
textCamera.OutlineThickness = 3;
textCamera.OutlineColor = Color.Black;
RegisterCommands();
Log("== Otter Console Initialized!");
Log("Use 'help' to see available commands.");
Log("", false);
IsOpen = false;
dismissFor = 0;
}
internal void UpdateSurface() {
renderSurface = new Surface((int)game.WindowWidth, (int)game.WindowHeight);
renderSurface.CenterOrigin();
renderSurface.X = game.Surface.X;
renderSurface.Y = game.Surface.Y;
renderSurface.Smooth = false;
cameraMoveRate = (int)((renderSurface.Width + renderSurface.Height) * 0.5f * 0.1f);
imgOverlay = Image.CreateRectangle(renderSurface.Width, renderSurface.Height, Color.Black);
imgOverlay.Scroll = 0;
imgError = Image.CreateRectangle(renderSurface.Width, renderSurface.Height, Color.Red);
imgError.Scroll = 0;
imgError.Alpha = 0;
float fontScale = Util.ScaleClamp(renderSurface.Height, 400, 800, 0.67f, 1);
padding = (int)Util.Clamp(paddingMax * fontScale, paddingMax * 0.25f, paddingMax);
textInput.FontSize = (int)(textSizeLarge * fontScale);
textInputHelp.FontSize = (int)(textSizeLarge * fontScale);
textPastCommands.FontSize = (int)(textSizeMedium * fontScale);
textPastCommandsLive.FontSize = (int)(textSizeMedium * fontScale);
textCommandsBuffered.FontSize = (int)(textSizeSmall * fontScale);
textCountdown.FontSize = (int)(textSizeHuge * fontScale);
textFramesLeft.FontSize = (int)(textSizeMedium * fontScale);
textPerformance.FontSize = (int)(textSizeMedium * fontScale);
textCamera.FontSize = (int)(textSizeLarge * fontScale);
imgOtter.Scale = fontScale;
textFramesLeft.Y = renderSurface.Height - textFramesLeft.LineSpacing;
textInput.Y = renderSurface.Height - textInput.LineSpacing - padding;
textInput.X = padding;
textInputHelp.SetPosition(textInput, 0, -24);
textCommandsBuffered.X = textInput.X;
textCommandsBuffered.Y = textInput.Y + textInput.LineSpacing + 3;
textAreaHeight = (int)(renderSurface.Height - padding * 3 - textInput.LineSpacing);
maxLines = (int)(textAreaHeight / textPastCommands.LineSpacing);
maxChars = (int)((renderSurface.Width - padding * 2) / (textInput.FontSize * 0.6));
textPastCommands.Y = padding;
textPastCommands.X = padding;
textPastCommandsLive.Y = padding / 2;
textPastCommandsLive.X = padding / 2;
textCountdown.X = renderSurface.HalfWidth;
textCountdown.Y = renderSurface.HalfHeight;
imgScrollBarBg = Image.CreateRectangle(scrollBarWidth, textAreaHeight, Color.Black);
imgScrollBar = Image.CreateRectangle(scrollBarWidth, textAreaHeight, Color.White);
imgScrollBarBg.X = renderSurface.Width - padding - imgScrollBarBg.Width;
imgScrollBarBg.Y = padding;
imgScrollBar.X = imgScrollBarBg.X;
imgScrollBar.Y = imgScrollBarBg.Y;
imgOtter.X = renderSurface.HalfWidth;
imgOtter.Y = renderSurface.HalfHeight;
imgScrollBar.Scroll = 0;
imgScrollBarBg.Scroll = 0;
textCamera.CenterTextOrigin();
textCamera.X = renderSurface.HalfWidth;
textCamera.Y = renderSurface.Height - padding - textCamera.LineSpacing;
}
internal void WindowInit() {
if (IsOpen) {
RemoveInput();
AddInput();
}
game.Window.KeyPressed += OnKeyPressedToggle;
}
internal void AddInput() {
game.Window.TextEntered += OnTextEntered;
game.Window.KeyPressed += OnKeyPressed;
game.Window.MouseWheelMoved += OnMouseWheel;
game.Window.KeyReleased += OnKeyReleased;
}
internal void RemoveInput() {
game.Window.TextEntered -= OnTextEntered;
game.Window.KeyPressed -= OnKeyPressed;
game.Window.MouseWheelMoved -= OnMouseWheel;
game.Window.KeyReleased -= OnKeyReleased;
}
internal Game game;
internal void Update() {
Instance = this;
UpdatePerformance();
if (currentState == stateNormal) {
if (cameraTogglePressed) {
cameraTogglePressed = false;
currentState = stateCamera;
commandInputEnabled = false;
Visible = false;
}
if (toggleKeyPressed) {
toggleKeyPressed = false;
if (!IsOpen) {
Summon();
}
}
if (dismissFor > 0) {
int framesLeft = advanceFrames - dismissFor;
textFramesLeft.String = "Update " + framesLeft.ToString("000") + "/" + advanceFrames.ToString("000");
dismissFor--;
if (dismissFor == 0) {
// set a flag here or something
autoSummon = true;
Summon();
}
}
if (dismissPressed) {
Dismiss();
}
if (!IsOpen) {
return;
}
textCountdown.String = "";
if (countDownTimer > 0) {
countDownTimer -= game.DeltaTime;
if (countDownTimer <= 0) {
dismissFor = advanceFrames;
locked = false;
Dismiss(false);
countDownTimer = 0;
}
if (game.MeasureTimeInFrames && game.FixedFramerate) {
textCountdown.String = countDownTimer.ToString("STARTING IN 00");
}
else {
textCountdown.String = countDownTimer.ToString("STARTING IN 00.00");
}
textCountdown.CenterOrigin();
return;
}
}
else if (currentState == stateCamera) {
if (cameraTogglePressed) {
cameraTogglePressed = false;
commandInputEnabled = true;
currentState = stateNormal;
Visible = true;
}
DebugCameraX += (debugCamX - DebugCameraX) * 0.25f;
DebugCameraY += (debugCamY - DebugCameraY) * 0.25f;
if (Scene.Instance != null) {
Scene.Instance.UpdateCamera();
}
}
imgOverlay.Alpha = Util.Approach(imgOverlay.Alpha, backgroundAlpha, 0.05f);
imgScrollBar.Alpha = imgScrollBarBg.Alpha = imgOverlay.Alpha;
imgOtter.Alpha = imgOverlay.Alpha * 0.25f;
imgError.Alpha = Util.Approach(imgError.Alpha, 0, 0.02f);
string displayString = keyString;
if (keyString.Length > maxChars) displayString = keyString.Substring(keyString.Length - maxChars);
textInput.String = "> " + displayString + "|";
imgScrollBar.ScaledHeight = maxLines / Util.Max(debugLog.Count, maxLines) * textAreaHeight;
int logMax = (int)Util.Max(debugLog.Count - maxLines, 0);
int scrollpos = (int)Util.Floor(Util.ScaleClamp(logIndex, 0, logMax, 0, textAreaHeight - imgScrollBar.ScaledHeight));
imgScrollBar.Y = padding + scrollpos;
if (enterPressed) {
SendCommand(keyString);
}
time += game.DeltaTime;
}
internal void Dismiss(bool execute = true) {
if (!IsOpen) return;
if (execute) ExecuteCommands();
ClearKeystring();
if (!executionError) {
RemoveInput();
IsOpen = false;
game.Input.bufferReleases = true;
Visible = false;
game.ShowDebugger = false;
DebugCameraX = 0;
DebugCameraY = 0;
}
else {
ErrorFlash();
UpdateConsoleText();
executionError = false;
}
dismissPressed = false;
}
internal void Render() {
game.countRendering = false;
var tempTarget = Draw.Target;
Draw.SetTarget(renderSurface);
Draw.Graphic(textPerformance, x, y);
if (dismissFor > 0) {
Draw.Graphic(textFramesLeft, x, y);
}
if (Visible) {
Draw.Graphic(imgOverlay, x, y);
Draw.Graphic(imgOtter, x, y);
Draw.Graphic(imgError, x, y);
if (countDownTimer > 0) {
Draw.Graphic(textCountdown, x, y);
}
else {
Draw.Graphic(imgScrollBarBg, x, y);
Draw.Graphic(imgScrollBar, x, y);
Draw.Graphic(textInput, x, y);
Draw.Graphic(textInputHelp, x, y);
Draw.Graphic(textPastCommands, x, y);
Draw.Graphic(textCommandsBuffered, x, y);
}
}
else {
if (liveConsoleLines > 0) Draw.Graphic(textPastCommandsLive, x, y);
}
if (currentState == stateCamera) {
Draw.Graphic(textCamera, x, y);
}
Draw.SetTarget(tempTarget);
renderSurface.DrawToWindow(game);
game.countRendering = true;
}
#endregion
}
public class OtterCommand : Attribute {
/// <summary>
/// The string that can be typed into the console to invoke this method.
/// </summary>
public string Alias;
/// <summary>
/// The text that will appear when the method is called with no parameters (note: will never show up if the method has no parameters by default.)
/// </summary>
public string UsageText;
/// <summary>
/// The text that will appear along with the method when the user invokes the help command.
/// </summary>
public string HelpText;
/// <summary>
/// The method group to associate this method with. Groups can be added or removed during runtime.
/// </summary>
public string Group;
/// <summary>
/// If true the method will not run until the next update.
/// </summary>
public bool IsBuffered;
/// <summary>
/// Use named parameters to define this to make your life way easier.
/// </summary>
/// <param name="alias">The string that can be typed into the console to invoke this method.</param>
/// <param name="usageText">The text that will appear when the method is called with no parameters (note: will never show up if the method has no parameters by default.)</param>
/// <param name="helpText">The text that will appear along with the method when the user invokes the help command.</param>
/// <param name="group">The method group to associate this method with. Groups can be added or removed during runtime.</param>
/// <param name="isBuffered">If true the method will not run until the next update.</param>
public OtterCommand(string alias = "", string usageText = "", string helpText = "", string group = "", bool isBuffered = false) {
Alias = alias;
UsageText = usageText;
HelpText = helpText;
Group = group;
IsBuffered = isBuffered;
}
}
}