initial commit of project

This commit is contained in:
That_One_Nerd 2023-03-16 07:24:39 -04:00
parent 89bacdda5a
commit 6979b3d99e
25 changed files with 912 additions and 1 deletions

8
.gitignore vendored Normal file
View File

@ -0,0 +1,8 @@
# Visual Studio stuff
.vs/
SrcMod/.vs/
*.sln
# Compiled Files
SrcMod/Compiled
SrcMod/Shell/obj/

View File

@ -1,2 +1,17 @@
# SrcMod
SrcMod *will be* a command-line Source Engine modding tool.
## About
SrcMod is a command-line [Source Engine](https://developer.valvesoftware.com/wiki/Source) modding tool. It's currently in alpha development and will be developed further in the future.
---
SrcMod is a tool I originally decided to make so I wouldn't have to do all of the setup and finicky stuff required to develop a Source Engine mod. It runs its own shell and has a set of commands that can be entered. This tool is expected to near a working product by the beginning of summer. No promises though.
## Roadmap
I don't have any specific deadlines for anything but the first official release, which will *hopefully* be around the beginning of summer. However, currently the shell is in a state where no actual modding functionality is implemented and it's just a super simple shell. During the alpha releases, it will remain that way. When modding functionality is finally added, the development will shift into beta development.
In the official releases, an installer will accompany the shell, but in the alpha and beta releases the shell will have to be installed manually. The exact directory of the shell doesn't particularly matter.
---
More to come!

BIN
SrcMod/Assets/Logo 128p.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.7 KiB

BIN
SrcMod/Assets/Logo 16p.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 700 B

BIN
SrcMod/Assets/Logo 256p.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

BIN
SrcMod/Assets/Logo 32p.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

BIN
SrcMod/Assets/Logo 64p.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

BIN
SrcMod/Assets/Logo 8p.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 341 B

BIN
SrcMod/Assets/Logo Max.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

BIN
SrcMod/Assets/Logo.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 352 KiB

19
SrcMod/Shell/Game.cs Normal file
View File

@ -0,0 +1,19 @@
namespace SrcMod.Shell;
public class Game
{
public static readonly Game Portal2 = new()
{
Name = "Portal 2",
NameId = "portal2",
SteamId = 620
};
public required string Name { get; init; }
public required string NameId { get; init; }
public required int SteamId { get; init; }
private Game() { }
public override string ToString() => Name;
}

View File

@ -0,0 +1,10 @@
global using Nerd_STF.Mathematics;
global using SrcMod.Shell;
global using SrcMod.Shell.Modules.ObjectModels;
global using System;
global using System.Collections.Generic;
global using System.Diagnostics;
global using System.IO;
global using System.Linq;
global using System.Reflection;
global using static SrcMod.Shell.Tools;

View File

@ -0,0 +1,17 @@
namespace SrcMod.Shell;
public struct HistoryItem
{
public required Action action;
public required string name;
public DateTime timestamp;
public HistoryItem()
{
timestamp = DateTime.Now;
}
public void Invoke() => action.Invoke();
public override string ToString() => $"{timestamp:MM/dd/yyyy HH:mm:ss} | {name}";
}

BIN
SrcMod/Shell/Logo.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 352 KiB

25
SrcMod/Shell/Mod.cs Normal file
View File

@ -0,0 +1,25 @@
namespace SrcMod.Shell;
public class Mod
{
public string Name { get; set; }
private Mod()
{
Name = string.Empty;
}
public static Mod? ReadDirectory(string dir)
{
if (!File.Exists(dir + "\\GameInfo.txt")) return null;
Mod mod = new()
{
Name = dir.Split("\\").Last()
};
return mod;
}
public override string ToString() => Name;
}

View File

@ -0,0 +1,258 @@
using System.IO;
using System.IO.Compression;
namespace SrcMod.Shell.Modules;
[Module("base", false)]
public static class BaseModule
{
[Command("cd")]
public static void ChangeDirectory(string newLocalPath)
{
string curDir = Program.Shell!.WorkingDirectory,
newDir = Path.GetFullPath(Path.Combine(curDir, newLocalPath));
Program.Shell!.UpdateWorkingDirectory(newDir);
Environment.CurrentDirectory = newDir;
}
[Command("clear")]
public static void ClearConsole()
{
Console.Clear();
Console.Write("\x1b[3J");
}
[Command("compress")]
public static void CompressFile(CompressedFileType type, string source, string? destination = null,
CompressionLevel level = CompressionLevel.Optimal)
{
destination ??= Path.Combine(Path.GetDirectoryName(source)!,
$"{Path.GetFileNameWithoutExtension(source)}.{type.ToString().ToLower()}");
string absSource = Path.GetFullPath(source),
absDest = Path.GetFullPath(destination);
switch (type)
{
case CompressedFileType.Zip:
if (File.Exists(source))
{
if (File.Exists(destination)) throw new($"File already exists at \"{destination}\"");
string message = $"Compressing file at \"{source}\" into \"{destination}\"...";
Write(message);
Stream writer = new FileStream(absDest, FileMode.CreateNew);
ZipArchive archive = new(writer, ZipArchiveMode.Create);
archive.CreateEntryFromFile(absSource, Path.GetFileName(absSource));
archive.Dispose();
writer.Dispose();
Console.CursorLeft = 0;
Console.CursorTop -= (message.Length / Console.BufferWidth) + 1;
Write(new string(' ', message.Length), newLine: false);
}
else if (Directory.Exists(source))
{
if (File.Exists(destination)) throw new($"File already exists at \"{destination}\"");
int consolePos = Console.CursorTop;
Write($"Compressing folder at \"{source}\" into \"{destination}\"...");
Stream writer = new FileStream(absDest, FileMode.CreateNew);
ZipArchive archive = new(writer, ZipArchiveMode.Create);
List<string> files = new(GetAllFiles(absSource)),
relative = new();
foreach (string f in files) relative.Add(Path.GetRelativePath(absSource, f));
LoadingBarStart();
for (int i = 0; i < files.Count; i++)
{
archive.CreateEntryFromFile(files[i], relative[i], level);
LoadingBarSet((i + 1) / (float)files.Count, ConsoleColor.DarkMagenta);
Console.CursorLeft = 0;
string message = $"{relative[i]}";
int remainder = Console.BufferWidth - message.Length;
if (remainder >= 0) message += new string(' ', remainder);
else message = $"...{message[(3 - remainder)..]}";
Write(message, newLine: false);
}
archive.Dispose();
writer.Dispose();
LoadingBarEnd();
Console.CursorLeft = 0;
Write(new string(' ', Console.BufferWidth), newLine: false);
Console.SetCursorPosition(0, Console.CursorTop - 2);
Write(new string(' ', Console.BufferWidth), newLine: false);
}
else throw new("No file or directory located at \"source\"");
break;
default: throw new($"Unknown type: \"{type}\"");
}
DateTime stamp = DateTime.Now;
Program.Shell!.AddHistory(new()
{
action = delegate
{
if (!File.Exists(absDest))
{
Write("Looks like the job is already completed Boss.", ConsoleColor.DarkYellow);
return;
}
FileInfo info = new(absDest);
if ((info.LastWriteTime - stamp).TotalMilliseconds >= 10)
throw new("The archive has been modified and probably shouldn't be undone.");
File.Delete(absDest);
},
name = $"Compressed a file or folder into a {type} archive located at \"{destination}\""
});
}
[Command("del")]
public static void Delete(string path)
{
if (File.Exists(path))
{
string tempFile = Path.GetTempFileName();
File.Delete(tempFile);
File.Copy(path, tempFile);
File.Delete(path);
Program.Shell!.AddHistory(new()
{
action = delegate
{
if (File.Exists(path)) throw new("Can't overwrite already existing file.");
File.Copy(tempFile, path);
File.Delete(tempFile);
},
name = $"Deleted file \"{Path.GetFileName(path)}\""
});
}
else if (Directory.Exists(path))
{
string[] parts = path.Replace("/", "\\").Split('\\',
StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries);
DirectoryInfo tempDir = Directory.CreateTempSubdirectory();
Directory.Delete(tempDir.FullName);
Directory.Move(path, tempDir.FullName);
Program.Shell!.AddHistory(new()
{
action = delegate
{
if (Directory.Exists(path)) throw new("Can't overwrite already existing file.");
Directory.Move(tempDir.FullName, path);
},
name = $"Deleted directory \"{parts.Last()}\""
});
}
else throw new($"No file or directory exists at \"{path}\"");
}
[Command("dir")]
public static void ListFilesAndDirs(string path = ".")
{
string[] dirs = Directory.GetDirectories(path),
files = Directory.GetFiles(path);
List<string> lines = new();
int longestName = 0,
longestSize = 0;
foreach (string d in dirs) if (d.Length > longestName) longestName = d.Length;
foreach (string f in files)
{
FileInfo info = new(f);
if (f.Length > longestName) longestName = f.Trim().Length;
int size = Mathf.Ceiling(MathF.Log10(info.Length));
if (longestSize > size) longestSize = size;
}
string header = $" Type Name{new string(' ', longestName - 4)}Date Modified File Size" +
$"{new string(' ', Mathf.Max(0, longestSize - 10) + 1)}";
lines.Add($"{header}\n{new string('-', header.Length)}");
foreach (string d in dirs)
{
DirectoryInfo info = new(d);
lines.Add($" Directory {info.Name.Trim()}{new string(' ', longestName - info.Name.Trim().Length)}" +
$"{info.LastWriteTime:MM/dd/yyyy HH:mm:ss}");
}
foreach (string f in files)
{
FileInfo info = new(f);
lines.Add($" File {info.Name.Trim()}{new string(' ', longestName - info.Name.Trim().Length)}" +
$"{info.LastWriteTime:MM/dd/yyyy HH:mm:ss} {info.Length}");
}
DisplayWithPages(lines);
}
[Command("echo")]
public static void Echo(string msg) => Write(msg);
[Command("exit")]
public static void ExitShell(int code = 0) => QuitShell(code);
[Command("explorer")]
public static void OpenExplorer(string path = ".") => Process.Start("explorer.exe", Path.GetFullPath(path));
[Command("history")]
public static void ShowHistory()
{
List<string> lines = new();
for (int i = lines.Count - 1; i >= 0; i--) lines.Add(Program.Shell!.History[i].ToString());
DisplayWithPages(lines);
}
[Command("permdel")]
public static void ReallyDelete(string path)
{
if (File.Exists(path)) File.Delete(path);
else if (Directory.Exists(path)) Directory.Delete(path);
else throw new($"No file or directory exists at \"{path}\"");
}
[Command("quit")]
public static void QuitShell(int code = 0)
{
Environment.Exit(code);
}
[Command("undo")]
public static void UndoCommand(int amount = 1)
{
for (int i = 0; i < amount; i++)
{
if (Program.Shell!.History.Count < 1)
{
if (i == 0) throw new("No operations to undo.");
else
{
Write("No more operations to undo.", ConsoleColor.DarkYellow);
break;
}
}
Program.Shell!.UndoItem();
}
}
public enum CompressedFileType
{
Zip
}
}

View File

@ -0,0 +1,12 @@
namespace SrcMod.Shell.Modules.ObjectModels;
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = false)]
public class CommandAttribute : Attribute
{
public readonly string NameId;
public CommandAttribute(string nameId)
{
NameId = nameId;
}
}

View File

@ -0,0 +1,69 @@
namespace SrcMod.Shell.Modules.ObjectModels;
public class CommandInfo
{
public required ModuleInfo Module { get; init; }
public required MethodInfo Method { get; init; }
public string Name { get; private set; }
public string NameId { get; private set; }
public ParameterInfo[] Parameters { get; private set; }
public int RequiredParameters { get; private set; }
private CommandInfo()
{
Name = string.Empty;
NameId = string.Empty;
Parameters = Array.Empty<ParameterInfo>();
RequiredParameters = 0;
}
public static CommandInfo? FromMethod(ModuleInfo parentModule, MethodInfo info)
{
CommandAttribute? attribute = info.GetCustomAttribute<CommandAttribute>();
if (attribute is null) return null;
if (info.ReturnType != typeof(void)) return null;
ParameterInfo[] param = info.GetParameters();
int required = 0;
while (required < param.Length && !param[required].IsOptional) required++;
return new()
{
Method = info,
Module = parentModule,
Name = info.Name,
NameId = attribute.NameId,
Parameters = param,
RequiredParameters = required
};
}
public void Invoke(params string[] args)
{
if (args.Length < RequiredParameters) throw new("Too few arguments. You must supply at least " +
$"{RequiredParameters}.");
if (args.Length > Parameters.Length) throw new("Too many parameters. You must supply no more than " +
$"{Parameters.Length}.");
object?[] invokes = new object?[Parameters.Length];
for (int i = 0; i < invokes.Length; i++)
{
if (i < args.Length)
{
string msg = args[i];
Type paramType = Parameters[i].ParameterType;
object? val = TypeParsers.ParseAll(msg);
if (val is string && paramType.IsEnum)
{
if (Enum.TryParse(paramType, msg, true, out object? possible)) val = possible;
}
val = Convert.ChangeType(val, paramType);
invokes[i] = val;
}
else invokes[i] = Parameters[i].DefaultValue;
}
Method.Invoke(Module.Instance, invokes);
}
}

View File

@ -0,0 +1,14 @@
namespace SrcMod.Shell.Modules.ObjectModels;
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)]
public class ModuleAttribute : Attribute
{
public readonly bool NameIsPrefix;
public readonly string NameId;
public ModuleAttribute(string nameId, bool nameIsPrefix = true)
{
NameId = nameId;
NameIsPrefix = nameIsPrefix;
}
}

View File

@ -0,0 +1,49 @@
namespace SrcMod.Shell.Modules.ObjectModels;
public class ModuleInfo
{
public List<CommandInfo> Commands { get; init; }
public object? Instance { get; init; }
public string Name { get; init; }
public string NameId { get; init; }
public bool NameIsPrefix { get; init; }
public required Type Type { get; init; }
private ModuleInfo()
{
Commands = new();
Instance = null;
Name = string.Empty;
NameId = string.Empty;
NameIsPrefix = true;
}
public static ModuleInfo? FromModule(Type info)
{
ModuleAttribute? attribute = info.GetCustomAttribute<ModuleAttribute>();
if (attribute is null) return null;
object? instance = info.IsAbstract ? null : Activator.CreateInstance(info);
ModuleInfo module = new()
{
Instance = instance,
Name = info.Name,
NameId = attribute.NameId,
NameIsPrefix = attribute.NameIsPrefix,
Type = info
};
List<CommandInfo> commands = new();
foreach (MethodInfo method in info.GetMethods())
{
CommandInfo? cmd = CommandInfo.FromMethod(module, method);
if (cmd is null) continue;
commands.Add(cmd);
}
module.Commands.AddRange(commands);
return module;
}
}

View File

@ -0,0 +1,36 @@
namespace SrcMod.Shell.Modules.ObjectModels;
public static class TypeParsers
{
public static object ParseAll(string msg)
{
if (TryParse(msg, out sbyte int8)) return int8;
if (TryParse(msg, out byte uInt8)) return uInt8;
if (TryParse(msg, out short int16)) return int16;
if (TryParse(msg, out ushort uInt16)) return uInt16;
if (TryParse(msg, out int int32)) return int32;
if (TryParse(msg, out uint uInt32)) return uInt32;
if (TryParse(msg, out long int64)) return int64;
if (TryParse(msg, out ulong uInt64)) return uInt64;
if (TryParse(msg, out Int128 int128)) return int128;
if (TryParse(msg, out UInt128 uInt128)) return uInt128;
if (TryParse(msg, out nint intPtr)) return intPtr;
if (TryParse(msg, out nuint uIntPtr)) return uIntPtr;
if (TryParse(msg, out Half float16)) return float16;
if (TryParse(msg, out float float32)) return float32;
if (TryParse(msg, out double float64)) return float64;
if (TryParse(msg, out decimal float128)) return float128;
if (TryParse(msg, out char resChar)) return resChar;
if (TryParse(msg, out DateOnly dateOnly)) return dateOnly;
if (TryParse(msg, out DateTime dateTime)) return dateTime;
if (TryParse(msg, out DateTime dateTimeOffset)) return dateTimeOffset;
if (TryParse(msg, out Guid guid)) return guid;
if (TryParse(msg, out TimeOnly timeOnly)) return timeOnly;
if (TryParse(msg, out TimeOnly timeSpan)) return timeSpan;
return msg;
}
public static bool TryParse<T>(string msg, out T? result) where T : IParsable<T>
=> T.TryParse(msg, null, out result);
}

25
SrcMod/Shell/Program.cs Normal file
View File

@ -0,0 +1,25 @@
namespace SrcMod.Shell;
public static class Program
{
public static Shell? Shell { get; private set; }
public static void Main(string[] args)
{
Console.Clear();
// Check for arguments and send a warning if they are found.
// In the future, I may use these arguments.
if (args.Length != 0) Write("[WARNING] You have supplied this shell " +
"with arguments. They will be ignored.", ConsoleColor.DarkYellow);
Shell = new();
while (true)
{
string cmd = Shell.ReadLine();
Shell.InvokeCommand(cmd);
Shell.ReloadDirectoryInfo();
}
}
}

190
SrcMod/Shell/Shell.cs Normal file
View File

@ -0,0 +1,190 @@
namespace SrcMod.Shell;
public class Shell
{
public const string Author = "That_One_Nerd";
public const string Name = "SrcMod";
public const string Version = "Alpha 0.1.0";
public readonly string? ShellDirectory;
public List<CommandInfo> LoadedCommands;
public List<ModuleInfo> LoadedModules;
public Game? ActiveGame;
public Mod? ActiveMod;
public List<HistoryItem> History;
public string WorkingDirectory;
public Shell()
{
Console.CursorVisible = false;
// Get shell directory and compare it to the path variable.
Assembly assembly = Assembly.GetExecutingAssembly();
string assemblyPath = assembly.Location;
if (string.IsNullOrWhiteSpace(assemblyPath) || !File.Exists(assemblyPath)) ShellDirectory = null;
ShellDirectory = Path.GetDirectoryName(assemblyPath)!.Replace("/", "\\");
if (ShellDirectory is null) Write("[ERROR] There was a problem detecting the shell's location. " +
"Many featues will be disabled.", ConsoleColor.Red);
// Check if the path in the PATH variable is correct.
string envVal = Environment.GetEnvironmentVariable("PATH", EnvironmentVariableTarget.User)!;
string[] pathParts = envVal.Split(";");
if (ShellDirectory is not null && !pathParts.Contains(ShellDirectory))
{
envVal += $"{ShellDirectory};";
Environment.SetEnvironmentVariable("PATH", envVal, EnvironmentVariableTarget.User);
Write($"[WARNING] The environment PATH does not contain the {Name} directory. It has now been added " +
"automatically. Is this your first time running the shell?", ConsoleColor.DarkYellow);
}
WorkingDirectory = Directory.GetCurrentDirectory();
// Load modules and commands.
LoadedModules = new();
LoadedCommands = new();
Type[] possibleModules = assembly.GetTypes();
foreach (Type t in possibleModules)
{
ModuleInfo? module = ModuleInfo.FromModule(t);
if (module is not null)
{
LoadedModules.Add(module);
LoadedCommands.AddRange(module.Commands);
}
}
// Other stuff
History = new();
// Send welcome message.
Write("\nWelcome to ", ConsoleColor.White, false);
Write($"{Name} {Version}", ConsoleColor.DarkCyan, false);
Write(" by ", ConsoleColor.White, false);
Write($"{Author}", ConsoleColor.DarkYellow);
ActiveGame = null;
ReloadDirectoryInfo();
}
public void AddHistory(HistoryItem item) => History.Add(item);
public void UndoItem()
{
HistoryItem item = History.Last();
item.Invoke();
History.RemoveAt(History.Count - 1);
Write($"Undid \"", newLine: false);
Write(item.name, ConsoleColor.White, false);
Write("\"");
}
public void UpdateWorkingDirectory(string dir)
{
string global = Path.GetFullPath(dir.Replace("/", "\\"), WorkingDirectory);
Directory.SetCurrentDirectory(global);
WorkingDirectory = global;
ReloadDirectoryInfo();
}
public string ReadLine()
{
Console.CursorVisible = true;
Write($"\n{WorkingDirectory}", ConsoleColor.DarkGreen, false);
if (ActiveGame is not null) Write($" {ActiveGame}", ConsoleColor.DarkYellow, false);
if (ActiveMod is not null) Write($" {ActiveMod}", ConsoleColor.Magenta, false);
Write(null);
Write($" {Name}", ConsoleColor.DarkCyan, false);
Write(" > ", ConsoleColor.White, false);
Console.ForegroundColor = ConsoleColor.White;
string message = Console.ReadLine()!;
Console.ResetColor();
Console.CursorVisible = false;
return message;
}
public void InvokeCommand(string cmd)
{
List<string> parts = new();
string active = string.Empty;
bool inQuotes = false;
for (int i = 0; i < cmd.Length; i++)
{
char c = cmd[i];
if (c == '\"' && i > 0 && cmd[i - 1] != '\\') inQuotes = !inQuotes;
else if (c == ' ' && !inQuotes)
{
if (string.IsNullOrWhiteSpace(active)) continue;
if (active.StartsWith('\"') && active.EndsWith('\"')) active = active[1..^1];
parts.Add(active);
active = string.Empty;
}
else active += c;
}
if (!string.IsNullOrWhiteSpace(active))
{
if (active.StartsWith('\"') && active.EndsWith('\"')) active = active[1..^1];
parts.Add(active);
}
if (parts.Count < 1) return;
string moduleName = parts[0].Trim().ToLower();
foreach (ModuleInfo module in LoadedModules)
{
if (module.NameIsPrefix && module.NameId.Trim().ToLower() != moduleName) continue;
string commandName;
if (module.NameIsPrefix)
{
if (parts.Count < 2) continue;
commandName = parts[1].Trim().ToLower();
}
else commandName = moduleName;
foreach (CommandInfo command in module.Commands)
{
if (command.NameId.Trim().ToLower() != commandName) continue;
int start = module.NameIsPrefix ? 2 : 1;
string[] args = parts.GetRange(start, parts.Count - start).ToArray();
try
{
command.Invoke(args);
}
catch (TargetInvocationException ex)
{
Write($"[ERROR] {ex.InnerException!.Message}", ConsoleColor.Red);
}
catch (Exception ex)
{
Write($"[ERROR] {ex.Message}", ConsoleColor.Red);
}
return;
}
}
Write($"[ERROR] Could not find command \"{cmd}\".", ConsoleColor.Red);
}
public void ReloadDirectoryInfo()
{
ActiveMod = Mod.ReadDirectory(WorkingDirectory);
// Update title.
string title = "SrcMod";
if (ActiveMod is not null) title += $" - {ActiveMod.Name}";
Console.Title = title;
}
}

35
SrcMod/Shell/Shell.csproj Normal file
View File

@ -0,0 +1,35 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net7.0</TargetFramework>
<ImplicitUsings>disable</ImplicitUsings>
<Nullable>enable</Nullable>
<AssemblyName>srcmod</AssemblyName>
<RootNamespace>SrcMod.Shell</RootNamespace>
<OutputPath>../Compiled/Shell</OutputPath>
<Title>SrcMod Shell</Title>
<Authors>That_One_Nerd</Authors>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
<ApplicationIcon>Logo.ico</ApplicationIcon>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<DebugType>embedded</DebugType>
<WarningLevel>9999</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
<DebugType>embedded</DebugType>
<WarningLevel>9999</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Content Include="Logo.ico" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Nerd_STF" Version="2.3.2" />
</ItemGroup>
</Project>

129
SrcMod/Shell/Tools.cs Normal file
View File

@ -0,0 +1,129 @@
using System.Net.Http.Headers;
namespace SrcMod.Shell;
public static class Tools
{
private static int loadingPosition = -1;
private static int lastBufferSize = 0;
private static int lastValue = -1;
public static void DisplayWithPages(IEnumerable<string> lines, ConsoleColor? color = null)
{
int written = 0;
bool multiPage = false, hasQuit = false;
foreach (string line in lines)
{
if (written == Console.BufferHeight - 2)
{
multiPage = true;
Console.BackgroundColor = ConsoleColor.Gray;
Console.ForegroundColor = ConsoleColor.Black;
Console.Write(" -- More -- ");
ConsoleKey key = Console.ReadKey(true).Key;
Console.ResetColor();
Console.CursorLeft = 0;
if (key == ConsoleKey.Q)
{
hasQuit = true;
break;
}
if (key == ConsoleKey.Spacebar) written = 0;
else written--;
}
Write(line, color);
written++;
}
if (multiPage)
{
Console.BackgroundColor = ConsoleColor.Gray;
Console.ForegroundColor = ConsoleColor.DarkGreen;
Console.Write(" -- End -- ");
while (!hasQuit && Console.ReadKey(true).Key != ConsoleKey.Q) ;
Console.ResetColor();
Console.CursorLeft = 0;
Console.Write(" ");
}
}
public static IEnumerable<string> GetAllFiles(string directory)
{
List<string> allFiles = new();
foreach (string f in Directory.GetFiles(directory)) allFiles.Add(Path.GetFullPath(f));
foreach (string dir in Directory.GetDirectories(directory)) allFiles.AddRange(GetAllFiles(dir));
return allFiles;
}
public static void LoadingBarEnd(bool clear = true)
{
if (loadingPosition == -1) throw new("No loading bar is active.");
if (clear)
{
Int2 oldPos = (Console.CursorLeft, Console.CursorTop);
Console.CursorLeft = 0;
Console.CursorTop = loadingPosition;
Console.Write(new string(' ', Console.BufferWidth));
Console.CursorLeft = 0;
Console.SetCursorPosition(oldPos.x, oldPos.y);
}
loadingPosition = -1;
}
public static void LoadingBarSet(float value, ConsoleColor? color = null)
{
const string left = " --- [",
right = "] --- ";
int barSize = Console.BufferWidth - left.Length - right.Length,
filled = (int)(barSize * value);
if (filled == lastValue) return;
Int2 oldPos = (Console.CursorLeft, Console.CursorTop);
// Erase last bar.
Console.SetCursorPosition(0, loadingPosition);
Console.Write(new string(' ', lastBufferSize));
Console.CursorLeft = 0;
// Add new bar.
lastBufferSize = Console.BufferWidth;
Write(left, newLine: false);
ConsoleColor oldFore = Console.ForegroundColor;
if (color is not null) Console.ForegroundColor = color.Value;
Write(new string('=', filled), newLine: false);
if (color is not null) Console.ForegroundColor = oldFore;
Write(new string(' ', barSize - filled), newLine: false);
Write(right, newLine: false);
if (oldPos.y == Console.CursorTop) oldPos.y++;
Console.SetCursorPosition(oldPos.x, oldPos.y);
}
public static void LoadingBarStart(float value = 0, int? position = null, ConsoleColor? color = null)
{
if (loadingPosition != -1) throw new("The loading bar has already been enabled.");
loadingPosition = position ?? Console.CursorTop;
LoadingBarSet(value, color);
}
public static void Write(object? message, ConsoleColor? col = null, bool newLine = true)
{
ConsoleColor prevCol = Console.ForegroundColor;
if (col is not null) Console.ForegroundColor = col.Value;
if (newLine) Console.WriteLine(message);
else Console.Write(message);
Console.ForegroundColor = prevCol;
}
}