mirror of
https://github.com/ServUO/ServUO
synced 2026-08-11 20:23:04 -04:00
* - Internal Galleon Update: Galleon Facing now uses the Multi Component List to change the ID's of fixtures. Some of the ID's (weapon pads) were not using the correct ID's, and this way the code is much more simplified. All fixture lists have been consolidated into one list. - Gargish Jewels no longer drop on non-SA Core - Fixed issue with trapped boxes - Added Event Sink for sending detailed house foundation packet. - Fixed issue where imbued jewelry weren't getting durability. * update solution fixed graphical issue * Bug Fixes - Added Blackthorne DUngeon stealables - Added proper base resists to Leather Ninja Hood - Medusa now drops Medusa Floor Tile Addon Deed (THANKS MILVA!) - AOSWeaponAttributes now work on Armor - Protection now expires after player death - Fixed crash in PVP Arena Gumps - Exceptional Damage Inc no longer gives extra points for Clean up Britannia Turn In - Fixed Loyalty Gump Crash - Fixed Stygian Dragon arty drops per EA * Potential Crash Fix * csproj update * Fixed conflict issue... WTF? * Bug Fixes *Requires Core Recompile* - Fixed cliloc on new belt craftables - failed craft no longer deletes crimson cincture and luck mempo - Added a core edit to Container.cs (had to do it folks) for better resource evaluation when considering resources to consume while crafting. In this instance, we don't want VvV or Faction items being used as craftable resources due to thier ease of obtaining. - Consolidated no-delete resources in CraftSystem.cs - Fellowship Data now serializes to prevent generation at each server start * Resolve Conflicts * Bug Fixes - Shield bash forumula no more EA like (still not perfect) - Healers now show to dead players (needs work) - Honor buff now persists through death - * crash fix * Initial Commit - Publish 105 Forgotten Treasures - Removed Remove trap skill prerequisites * Moongate Fix - You have to be 1 tile to double click it. All other range checks are correct. * Commit #2 * crash fix * Commit #3 * Crash Fix * Commit #3 * More updates and additions. Compiles! * Final Commit * Project Update * Shields no longer engraveable w/ armor engraving tool. * Fixed THunting Town Crier Quest Fixed issue where initial guardians were not spawning for level 1 maps * Compile error fix. * COmpire error #2 fix * Bug Fixes - Summoners Kilt is now repairable - Orange petals are now 1 stone each (HS era check) - Enabled altering artifact (IsArtifact) footwear - Altering certain artifact (IsArtifact) items now carry over the proper resistance bonuses - Fixed Primeval Lich Unholy Touch special ability - Orc brutes now have a chance to throw an orc on any damage Received - Name changes now show up on the paperdoll (you have to close/re-open) - Removed "Gravewater Lake" region. It was conflicting with region functionality in Exploring the Deep Quest - Shadowguard Orchard Encounter apples now spawn a treefellow on each player in the region when deleted and thrown at the incorrect Tree - Shanty the Pirate no longer does reflect damage attack - Animal Lore gump now shows adjusted stats, ie cursed/blessed - Vendor Maps in Tokuno no longer crash the client - Nether Blase cast delay moved to 2.0 seconds, per EA - Arcane Empowerment SDI buff now shows in the status bar - Wildfire should no longer stack * Bug Fixes - Toggling weapon specials now expires Injected Strike - Crafting stacked items will now drastically reduce the amount of skill gains, per EA - Fixed issue where items were invisible to players in castle courtyards - Fixed issue where Assistants were kicking EC clients during Handshake - Added proper area effects to Fire Beetle * Added singing ball * Bug Fixes - Increases Spinning Wheel timer to 6s - Attacking pets now use active speed when combatant is not a player, per EA - Fixed name issue for certain CUB pigments - Fixed issue where only young players could use stash treasure maps - Blackthorn invasion creatures will no longer be able to swim (water elementals) * Bug Fixes - Adjusted Frenzied Whirlwind min damage - Thiefs now train remove trap - Training remove trap no longer requires lockpicking and detect hidden - Death ray can no longer be stacked on single targets * Removed unused code * More adjustments. * Final Frenzied whirlwind fix * Bug Fixes - Treasure Maps now only spawn 1 ancient guardian at a time during remove trap - Players can now cast spells while digging up Chest - Removed skill wait time if chest is out of range - Nether blast mana rip no longer works if target has no mana * - Fixed Eodonian Potion Crash - Fixed Ararat Artifact Spawn Locations - Added Hues to TMap Guardians per EA * Bug Fixes - only crewman+ can now repair galleons - Updated Davie's Locker to handle new TMap System * added doors * Nether Blast Nerf. Damage no longer stacks. Fixed reg for Spell Plague Scroll. * Crash Fix * Bug Fixes - Snake Charmers Flute now is crafted the proper hue - Clean up Brit now gives the proper points for t-maps - Fixed issue where t-chests could be opened, even if ancient guardian was still alive * Pets now heal owners who are poisoned. Shipwright in Tokuno docks now sell galleons. * Keeps track of script compile time Co-authored-by: TrueUO <knight.daniel.r@gmail.com>
933 lines
19 KiB
C#
933 lines
19 KiB
C#
#region References
|
|
using System;
|
|
using System.CodeDom.Compiler;
|
|
using System.Collections.Generic;
|
|
using System.Diagnostics;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Reflection;
|
|
using System.Security.Cryptography;
|
|
using System.Text;
|
|
|
|
using Microsoft.CSharp;
|
|
#endregion
|
|
|
|
namespace Server
|
|
{
|
|
public static class ScriptCompiler
|
|
{
|
|
public static Assembly[] Assemblies { get; set; }
|
|
|
|
private static readonly List<string> m_AdditionalReferences = new List<string>();
|
|
|
|
public static string[] GetReferenceAssemblies()
|
|
{
|
|
var list = new List<string>();
|
|
|
|
var path = Path.Combine(Core.BaseDirectory, "Data/Assemblies.cfg");
|
|
|
|
if (File.Exists(path))
|
|
{
|
|
using (var ip = new StreamReader(path))
|
|
{
|
|
string line;
|
|
|
|
while ((line = ip.ReadLine()) != null)
|
|
{
|
|
if (line.Length > 0 && !line.StartsWith("#"))
|
|
{
|
|
list.Add(line);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
list.Add(Core.ExePath);
|
|
|
|
list.AddRange(m_AdditionalReferences);
|
|
|
|
return list.ToArray();
|
|
}
|
|
|
|
public static string GetCompilerOptions(bool debug)
|
|
{
|
|
StringBuilder sb = null;
|
|
|
|
AppendCompilerOption(ref sb, "/d:ServUO");
|
|
|
|
AppendCompilerOption(ref sb, "/unsafe");
|
|
|
|
if (!debug)
|
|
{
|
|
AppendCompilerOption(ref sb, "/optimize");
|
|
}
|
|
else
|
|
{
|
|
AppendCompilerOption(ref sb, "/debug");
|
|
AppendCompilerOption(ref sb, "/d:DEBUG");
|
|
AppendCompilerOption(ref sb, "/d:TRACE");
|
|
}
|
|
|
|
#if MONO
|
|
AppendCompilerOption( ref sb, "/d:MONO" );
|
|
#endif
|
|
|
|
if (Core.Is64Bit)
|
|
{
|
|
AppendCompilerOption(ref sb, "/d:x64");
|
|
}
|
|
|
|
#if NEWTIMERS
|
|
AppendCompilerOption(ref sb, "/d:NEWTIMERS");
|
|
#endif
|
|
|
|
#if NEWPARENT
|
|
AppendCompilerOption(ref sb, "/d:NEWPARENT");
|
|
#endif
|
|
|
|
return (sb == null ? null : sb.ToString());
|
|
}
|
|
|
|
private static void AppendCompilerOption(ref StringBuilder sb, string define)
|
|
{
|
|
if (sb == null)
|
|
{
|
|
sb = new StringBuilder();
|
|
}
|
|
else
|
|
{
|
|
sb.Append(' ');
|
|
}
|
|
|
|
sb.Append(define);
|
|
}
|
|
|
|
private static byte[] GetHashCode(string compiledFile, string[] scriptFiles, bool debug)
|
|
{
|
|
using (var ms = new MemoryStream())
|
|
{
|
|
using (var bin = new BinaryWriter(ms))
|
|
{
|
|
var fileInfo = new FileInfo(compiledFile);
|
|
|
|
bin.Write(fileInfo.LastWriteTimeUtc.Ticks);
|
|
|
|
foreach (var scriptFile in scriptFiles)
|
|
{
|
|
fileInfo = new FileInfo(scriptFile);
|
|
|
|
bin.Write(fileInfo.LastWriteTimeUtc.Ticks);
|
|
}
|
|
|
|
bin.Write(debug);
|
|
bin.Write(Core.Version.ToString());
|
|
|
|
ms.Position = 0;
|
|
|
|
using (var sha1 = SHA1.Create())
|
|
{
|
|
return sha1.ComputeHash(ms);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public static bool CompileCSScripts(out Assembly assembly)
|
|
{
|
|
return CompileCSScripts(false, true, out assembly);
|
|
}
|
|
|
|
public static bool CompileCSScripts(bool debug, out Assembly assembly)
|
|
{
|
|
return CompileCSScripts(debug, true, out assembly);
|
|
}
|
|
|
|
public static bool CompileCSScripts(bool debug, bool cache, out Assembly assembly)
|
|
{
|
|
long startTime = Core.TickCount;
|
|
|
|
Utility.PushColor(ConsoleColor.Yellow);
|
|
Console.Write("Scripts: Compiling C# scripts...");
|
|
Utility.PopColor();
|
|
var files = GetScripts("*.cs");
|
|
|
|
if (files.Length == 0)
|
|
{
|
|
Utility.PushColor(ConsoleColor.Red);
|
|
Console.WriteLine("no files found.");
|
|
Utility.PopColor();
|
|
assembly = null;
|
|
return true;
|
|
}
|
|
|
|
if (File.Exists("Scripts/Output/Scripts.CS.dll"))
|
|
{
|
|
if (cache && File.Exists("Scripts/Output/Scripts.CS.hash"))
|
|
{
|
|
try
|
|
{
|
|
var hashCode = GetHashCode("Scripts/Output/Scripts.CS.dll", files, debug);
|
|
|
|
using (var fs = new FileStream("Scripts/Output/Scripts.CS.hash", FileMode.Open, FileAccess.Read, FileShare.Read))
|
|
{
|
|
using (var bin = new BinaryReader(fs))
|
|
{
|
|
var bytes = bin.ReadBytes(hashCode.Length);
|
|
|
|
if (bytes.Length == hashCode.Length)
|
|
{
|
|
var valid = true;
|
|
|
|
for (var i = 0; i < bytes.Length; ++i)
|
|
{
|
|
if (bytes[i] != hashCode[i])
|
|
{
|
|
valid = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (valid)
|
|
{
|
|
assembly = Assembly.LoadFrom("Scripts/Output/Scripts.CS.dll");
|
|
|
|
if (!m_AdditionalReferences.Contains(assembly.Location))
|
|
{
|
|
m_AdditionalReferences.Add(assembly.Location);
|
|
}
|
|
|
|
Utility.PushColor(ConsoleColor.Green);
|
|
Console.WriteLine("done (cached)");
|
|
Utility.PopColor();
|
|
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
catch
|
|
{ }
|
|
}
|
|
}
|
|
|
|
DeleteFiles("Scripts.CS*.dll");
|
|
|
|
#if !MONO
|
|
using (CodeDomProvider provider = new Microsoft.CodeDom.Providers.DotNetCompilerPlatform.CSharpCodeProvider())
|
|
#else
|
|
using (CSharpCodeProvider provider = new CSharpCodeProvider())
|
|
#endif
|
|
{
|
|
var path = GetUnusedPath("Scripts.CS");
|
|
|
|
var parms = new CompilerParameters(GetReferenceAssemblies(), path, debug);
|
|
|
|
var options = GetCompilerOptions(debug);
|
|
|
|
if (options != null)
|
|
{
|
|
parms.CompilerOptions = options;
|
|
}
|
|
|
|
if (Core.HaltOnWarning)
|
|
{
|
|
parms.WarningLevel = 4;
|
|
}
|
|
|
|
if (Core.Unix)
|
|
{
|
|
parms.CompilerOptions = String.Format( "{0} /nowarn:169,219,414 /recurse:Scripts/*.cs", parms.CompilerOptions );
|
|
files = new string[0];
|
|
}
|
|
|
|
var results = provider.CompileAssemblyFromFile(parms, files);
|
|
|
|
m_AdditionalReferences.Add(path);
|
|
|
|
Display(results, startTime);
|
|
|
|
if (results.Errors.Count > 0 && !Core.Unix)
|
|
{
|
|
assembly = null;
|
|
return false;
|
|
}
|
|
|
|
if (results.Errors.Count > 0 && Core.Unix)
|
|
{
|
|
foreach( CompilerError err in results.Errors ) {
|
|
if ( !err.IsWarning ) {
|
|
assembly = null;
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (cache && Path.GetFileName(path) == "Scripts.CS.dll")
|
|
{
|
|
try
|
|
{
|
|
var hashCode = GetHashCode(path, files, debug);
|
|
|
|
using (
|
|
var fs = new FileStream("Scripts/Output/Scripts.CS.hash", FileMode.Create, FileAccess.Write, FileShare.None))
|
|
{
|
|
using (var bin = new BinaryWriter(fs))
|
|
{
|
|
bin.Write(hashCode, 0, hashCode.Length);
|
|
}
|
|
}
|
|
}
|
|
catch
|
|
{ }
|
|
}
|
|
|
|
assembly = results.CompiledAssembly;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
public static void Display(CompilerResults results, long startTime)
|
|
{
|
|
if (results.Errors.Count > 0)
|
|
{
|
|
var errors = new Dictionary<string, List<CompilerError>>(results.Errors.Count, StringComparer.OrdinalIgnoreCase);
|
|
var warnings = new Dictionary<string, List<CompilerError>>(results.Errors.Count, StringComparer.OrdinalIgnoreCase);
|
|
|
|
foreach (CompilerError e in results.Errors)
|
|
{
|
|
var file = e.FileName;
|
|
|
|
// Ridiculous. FileName is null if the warning/error is internally generated in csc.
|
|
if (string.IsNullOrEmpty(file))
|
|
{
|
|
Console.WriteLine("ScriptCompiler: {0}: {1}", e.ErrorNumber, e.ErrorText);
|
|
continue;
|
|
}
|
|
|
|
var table = (e.IsWarning ? warnings : errors);
|
|
|
|
List<CompilerError> list = null;
|
|
table.TryGetValue(file, out list);
|
|
|
|
if (list == null)
|
|
{
|
|
table[file] = list = new List<CompilerError>();
|
|
}
|
|
|
|
list.Add(e);
|
|
}
|
|
|
|
if (errors.Count > 0)
|
|
{
|
|
Utility.PushColor(ConsoleColor.Red);
|
|
Console.WriteLine("Failed with: {0} errors, {1} warnings", errors.Count, warnings.Count);
|
|
Utility.PopColor();
|
|
}
|
|
else
|
|
{
|
|
Utility.PushColor(ConsoleColor.Green);
|
|
Console.WriteLine("Finished with: {0} errors, {1} warnings [{2} seconds]", errors.Count, warnings.Count, (int)((Core.TickCount - startTime) / 1000));
|
|
Utility.PopColor();
|
|
}
|
|
|
|
var scriptRoot = Path.GetFullPath(Path.Combine(Core.BaseDirectory, "Scripts" + Path.DirectorySeparatorChar));
|
|
var scriptRootUri = new Uri(scriptRoot);
|
|
|
|
Utility.PushColor(ConsoleColor.Yellow);
|
|
|
|
if (warnings.Count > 0)
|
|
{
|
|
Console.WriteLine("Warnings:");
|
|
}
|
|
|
|
foreach (var kvp in warnings)
|
|
{
|
|
var fileName = kvp.Key;
|
|
var list = kvp.Value;
|
|
|
|
var fullPath = Path.GetFullPath(fileName);
|
|
var usedPath = Uri.UnescapeDataString(scriptRootUri.MakeRelativeUri(new Uri(fullPath)).OriginalString);
|
|
|
|
Console.WriteLine(" + {0}:", usedPath);
|
|
|
|
Utility.PushColor(ConsoleColor.DarkYellow);
|
|
|
|
foreach (var e in list)
|
|
{
|
|
Console.WriteLine(" {0}: Line {1}: {2}", e.ErrorNumber, e.Line, e.ErrorText);
|
|
}
|
|
|
|
Utility.PopColor();
|
|
}
|
|
|
|
Utility.PopColor();
|
|
|
|
Utility.PushColor(ConsoleColor.Red);
|
|
|
|
if (errors.Count > 0)
|
|
{
|
|
Console.WriteLine("Errors:");
|
|
}
|
|
|
|
foreach (var kvp in errors)
|
|
{
|
|
var fileName = kvp.Key;
|
|
var list = kvp.Value;
|
|
|
|
var fullPath = Path.GetFullPath(fileName);
|
|
var usedPath = Uri.UnescapeDataString(scriptRootUri.MakeRelativeUri(new Uri(fullPath)).OriginalString);
|
|
|
|
Console.WriteLine(" + {0}:", usedPath);
|
|
|
|
Utility.PushColor(ConsoleColor.Red);
|
|
|
|
foreach (var e in list)
|
|
{
|
|
Console.WriteLine(" {0}: Line {1}: {2}", e.ErrorNumber, e.Line, e.ErrorText);
|
|
}
|
|
|
|
Utility.PopColor();
|
|
}
|
|
|
|
Utility.PopColor();
|
|
}
|
|
else
|
|
{
|
|
Utility.PushColor(ConsoleColor.Green);
|
|
Console.WriteLine("Finished with: 0 errors, 0 warnings [{0} seconds]", (int)((Core.TickCount - startTime) / 1000));
|
|
Utility.PopColor();
|
|
}
|
|
}
|
|
|
|
public static string GetUnusedPath(string name)
|
|
{
|
|
var path = Path.Combine(Core.BaseDirectory, String.Format("Scripts/Output/{0}.dll", name));
|
|
|
|
for (var i = 2; File.Exists(path) && i <= 1000; ++i)
|
|
{
|
|
path = Path.Combine(Core.BaseDirectory, String.Format("Scripts/Output/{0}.{1}.dll", name, i));
|
|
}
|
|
|
|
return path;
|
|
}
|
|
|
|
public static void DeleteFiles(string mask)
|
|
{
|
|
try
|
|
{
|
|
var files = Directory.GetFiles(Path.Combine(Core.BaseDirectory, "Scripts/Output"), mask);
|
|
|
|
foreach (var file in files)
|
|
{
|
|
try
|
|
{
|
|
File.Delete(file);
|
|
}
|
|
catch
|
|
{ }
|
|
}
|
|
}
|
|
catch
|
|
{ }
|
|
}
|
|
|
|
private delegate CompilerResults Compiler(bool debug);
|
|
|
|
public static bool Compile()
|
|
{
|
|
return Compile(false);
|
|
}
|
|
|
|
public static bool Compile(bool debug)
|
|
{
|
|
return Compile(debug, true);
|
|
}
|
|
|
|
public static bool Compile(bool debug, bool cache)
|
|
{
|
|
EnsureDirectory("Scripts/");
|
|
EnsureDirectory("Scripts/Output/");
|
|
|
|
if (m_AdditionalReferences.Count > 0)
|
|
{
|
|
m_AdditionalReferences.Clear();
|
|
}
|
|
|
|
var assemblies = new List<Assembly>();
|
|
|
|
Assembly assembly;
|
|
|
|
if (CompileCSScripts(debug, cache, out assembly))
|
|
{
|
|
if (assembly != null)
|
|
{
|
|
assemblies.Add(assembly);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (assemblies.Count == 0)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
Assemblies = assemblies.ToArray();
|
|
|
|
Utility.PushColor(ConsoleColor.Yellow);
|
|
Console.WriteLine("Scripts: Verifying...");
|
|
Utility.PopColor();
|
|
|
|
var watch = Stopwatch.StartNew();
|
|
|
|
Core.VerifySerialization();
|
|
|
|
watch.Stop();
|
|
|
|
Utility.PushColor(ConsoleColor.Green);
|
|
Console.WriteLine(
|
|
"Finished ({0} items, {1} mobiles, {2} customs) ({3:F2} seconds)",
|
|
Core.ScriptItems,
|
|
Core.ScriptMobiles,
|
|
Core.ScriptCustoms,
|
|
watch.Elapsed.TotalSeconds);
|
|
Utility.PopColor();
|
|
|
|
return true;
|
|
}
|
|
|
|
public static void Invoke(string method)
|
|
{
|
|
var invoke = new List<MethodInfo>();
|
|
|
|
foreach (var a in Assemblies)
|
|
{
|
|
var types = a.GetTypes();
|
|
|
|
foreach (var t in types)
|
|
{
|
|
var m = t.GetMethod(method, BindingFlags.Static | BindingFlags.Public);
|
|
|
|
if (m != null)
|
|
{
|
|
invoke.Add(m);
|
|
}
|
|
}
|
|
}
|
|
|
|
invoke.Sort(new CallPriorityComparer());
|
|
|
|
foreach (var m in invoke)
|
|
{
|
|
m.Invoke(null, null);
|
|
}
|
|
}
|
|
|
|
private static readonly Dictionary<Assembly, TypeCache> m_TypeCaches = new Dictionary<Assembly, TypeCache>();
|
|
private static TypeCache m_NullCache;
|
|
|
|
public static TypeCache GetTypeCache(Assembly asm)
|
|
{
|
|
if (asm == null)
|
|
{
|
|
return m_NullCache ?? (m_NullCache = new TypeCache(null));
|
|
}
|
|
|
|
TypeCache c;
|
|
|
|
m_TypeCaches.TryGetValue(asm, out c);
|
|
|
|
if (c == null)
|
|
{
|
|
m_TypeCaches[asm] = c = new TypeCache(asm);
|
|
}
|
|
|
|
return c;
|
|
}
|
|
|
|
public static Type FindTypeByFullName(string fullName)
|
|
{
|
|
return FindTypeByFullName(fullName, true);
|
|
}
|
|
|
|
public static Type FindTypeByFullName(string fullName, bool ignoreCase)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(fullName))
|
|
{
|
|
return null;
|
|
}
|
|
|
|
Type type = null;
|
|
|
|
for (var i = 0; type == null && i < Assemblies.Length; ++i)
|
|
{
|
|
type = GetTypeCache(Assemblies[i]).GetTypeByFullName(fullName, ignoreCase);
|
|
}
|
|
|
|
return type ?? GetTypeCache(Core.Assembly).GetTypeByFullName(fullName, ignoreCase);
|
|
}
|
|
|
|
public static IEnumerable<Type> FindTypesByFullName(string name)
|
|
{
|
|
return FindTypesByFullName(name, true);
|
|
}
|
|
|
|
public static IEnumerable<Type> FindTypesByFullName(string name, bool ignoreCase)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(name))
|
|
{
|
|
yield break;
|
|
}
|
|
|
|
for (var i = 0; i < Assemblies.Length; ++i)
|
|
{
|
|
foreach (var t in GetTypeCache(Assemblies[i]).GetTypesByFullName(name, ignoreCase))
|
|
{
|
|
yield return t;
|
|
}
|
|
}
|
|
|
|
foreach (var t in GetTypeCache(Core.Assembly).GetTypesByFullName(name, ignoreCase))
|
|
{
|
|
yield return t;
|
|
}
|
|
}
|
|
|
|
public static Type FindTypeByName(string name)
|
|
{
|
|
return FindTypeByName(name, true);
|
|
}
|
|
|
|
public static Type FindTypeByName(string name, bool ignoreCase)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(name))
|
|
{
|
|
return null;
|
|
}
|
|
|
|
Type type = null;
|
|
|
|
for (var i = 0; type == null && i < Assemblies.Length; ++i)
|
|
{
|
|
type = GetTypeCache(Assemblies[i]).GetTypeByName(name, ignoreCase);
|
|
}
|
|
|
|
return type ?? GetTypeCache(Core.Assembly).GetTypeByName(name, ignoreCase);
|
|
}
|
|
|
|
public static IEnumerable<Type> FindTypesByName(string name)
|
|
{
|
|
return FindTypesByName(name, true);
|
|
}
|
|
|
|
public static IEnumerable<Type> FindTypesByName(string name, bool ignoreCase)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(name))
|
|
{
|
|
yield break;
|
|
}
|
|
|
|
for (var i = 0; i < Assemblies.Length; ++i)
|
|
{
|
|
foreach (var t in GetTypeCache(Assemblies[i]).GetTypesByName(name, ignoreCase))
|
|
{
|
|
yield return t;
|
|
}
|
|
}
|
|
|
|
foreach (var t in GetTypeCache(Core.Assembly).GetTypesByName(name, ignoreCase))
|
|
{
|
|
yield return t;
|
|
}
|
|
}
|
|
|
|
public static void EnsureDirectory(string dir)
|
|
{
|
|
var path = Path.Combine(Core.BaseDirectory, dir);
|
|
|
|
if (!Directory.Exists(path))
|
|
{
|
|
Directory.CreateDirectory(path);
|
|
}
|
|
}
|
|
|
|
public static string[] GetScripts(string filter)
|
|
{
|
|
var list = new List<string>();
|
|
|
|
GetScripts(list, Path.Combine(Core.BaseDirectory, "Scripts"), filter);
|
|
|
|
return list.ToArray();
|
|
}
|
|
|
|
public static void GetScripts(List<string> list, string path, string filter)
|
|
{
|
|
foreach (var dir in Directory.GetDirectories(path))
|
|
{
|
|
GetScripts(list, dir, filter);
|
|
}
|
|
|
|
list.AddRange(Directory.GetFiles(path, filter));
|
|
}
|
|
}
|
|
|
|
public class TypeCache
|
|
{
|
|
private readonly Type[] m_Types;
|
|
private readonly TypeTable m_Names;
|
|
private readonly TypeTable m_FullNames;
|
|
|
|
public Type[] Types { get { return m_Types; } }
|
|
public TypeTable Names { get { return m_Names; } }
|
|
public TypeTable FullNames { get { return m_FullNames; } }
|
|
|
|
public Type GetTypeByName(string name, bool ignoreCase)
|
|
{
|
|
return GetTypesByName(name, ignoreCase).FirstOrDefault(t => t != null);
|
|
}
|
|
|
|
public IEnumerable<Type> GetTypesByName(string name, bool ignoreCase)
|
|
{
|
|
return m_Names.Get(name, ignoreCase);
|
|
}
|
|
|
|
public Type GetTypeByFullName(string fullName, bool ignoreCase)
|
|
{
|
|
return GetTypesByFullName(fullName, ignoreCase).FirstOrDefault(t => t != null);
|
|
}
|
|
|
|
public IEnumerable<Type> GetTypesByFullName(string fullName, bool ignoreCase)
|
|
{
|
|
return m_FullNames.Get(fullName, ignoreCase);
|
|
}
|
|
|
|
public TypeCache(Assembly asm)
|
|
{
|
|
if (asm == null)
|
|
{
|
|
m_Types = Type.EmptyTypes;
|
|
}
|
|
else
|
|
{
|
|
m_Types = asm.GetTypes();
|
|
}
|
|
|
|
m_Names = new TypeTable(m_Types.Length);
|
|
m_FullNames = new TypeTable(m_Types.Length);
|
|
|
|
foreach (var g in m_Types.ToLookup(t => t.Name))
|
|
{
|
|
m_Names.Add(g.Key, g);
|
|
|
|
foreach (var type in g)
|
|
{
|
|
m_FullNames.Add(type.FullName, type);
|
|
|
|
var attr = type.GetCustomAttribute<TypeAliasAttribute>(false);
|
|
|
|
if (attr != null)
|
|
{
|
|
foreach (var a in attr.Aliases)
|
|
{
|
|
m_FullNames.Add(a, type);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
m_Names.Prune();
|
|
m_FullNames.Prune();
|
|
|
|
m_Names.Sort();
|
|
m_FullNames.Sort();
|
|
}
|
|
}
|
|
|
|
public class TypeTable
|
|
{
|
|
private readonly Dictionary<string, List<Type>> m_Sensitive;
|
|
private readonly Dictionary<string, List<Type>> m_Insensitive;
|
|
|
|
public void Prune()
|
|
{
|
|
Prune(m_Sensitive);
|
|
Prune(m_Insensitive);
|
|
}
|
|
|
|
private static void Prune(Dictionary<string, List<Type>> types)
|
|
{
|
|
var buffer = new List<Type>();
|
|
|
|
foreach (var list in types.Values)
|
|
{
|
|
if (list.Count == 1)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
buffer.AddRange(list.Distinct());
|
|
|
|
list.Clear();
|
|
list.AddRange(buffer);
|
|
|
|
buffer.Clear();
|
|
}
|
|
|
|
buffer.TrimExcess();
|
|
}
|
|
|
|
public void Sort()
|
|
{
|
|
Sort(m_Sensitive);
|
|
Sort(m_Insensitive);
|
|
}
|
|
|
|
private static void Sort(Dictionary<string, List<Type>> types)
|
|
{
|
|
foreach (var list in types.Values)
|
|
{
|
|
list.Sort(InternalSort);
|
|
}
|
|
}
|
|
|
|
private static int InternalSort(Type l, Type r)
|
|
{
|
|
if (l == r)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
if (l != null && r == null)
|
|
{
|
|
return -1;
|
|
}
|
|
|
|
if (l == null && r != null)
|
|
{
|
|
return 1;
|
|
}
|
|
|
|
var a = IsEntity(l);
|
|
var b = IsEntity(r);
|
|
|
|
if (a && b)
|
|
{
|
|
a = IsConstructable(l, out var x);
|
|
b = IsConstructable(r, out var y);
|
|
|
|
if (a && !b)
|
|
{
|
|
return -1;
|
|
}
|
|
|
|
if (!a && b)
|
|
{
|
|
return 1;
|
|
}
|
|
|
|
return x > y ? -1 : x < y ? 1 : 0;
|
|
}
|
|
|
|
return a ? -1 : b ? 1 : 0;
|
|
}
|
|
|
|
private static bool IsEntity(Type type)
|
|
{
|
|
return type.GetInterface("IEntity") != null;
|
|
}
|
|
|
|
private static bool IsConstructable(Type type, out AccessLevel access)
|
|
{
|
|
foreach (var ctor in type.GetConstructors().OrderBy(o => o.GetParameters().Length))
|
|
{
|
|
var attr = ctor.GetCustomAttribute<ConstructableAttribute>(false);
|
|
|
|
if (attr != null)
|
|
{
|
|
access = attr.AccessLevel;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
access = 0;
|
|
return false;
|
|
}
|
|
|
|
public void Add(string key, IEnumerable<Type> types)
|
|
{
|
|
if (!string.IsNullOrWhiteSpace(key) && types != null)
|
|
{
|
|
Add(key, types.ToArray());
|
|
}
|
|
}
|
|
|
|
public void Add(string key, params Type[] types)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(key) || types == null || types.Length == 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (!m_Sensitive.TryGetValue(key, out var sensitive) || sensitive == null)
|
|
{
|
|
m_Sensitive[key] = new List<Type>(types);
|
|
}
|
|
else if (types.Length == 1)
|
|
{
|
|
sensitive.Add(types[0]);
|
|
}
|
|
else
|
|
{
|
|
sensitive.AddRange(types);
|
|
}
|
|
|
|
if (!m_Insensitive.TryGetValue(key, out var insensitive) || insensitive == null)
|
|
{
|
|
m_Insensitive[key] = new List<Type>(types);
|
|
}
|
|
else if (types.Length == 1)
|
|
{
|
|
insensitive.Add(types[0]);
|
|
}
|
|
else
|
|
{
|
|
insensitive.AddRange(types);
|
|
}
|
|
}
|
|
|
|
public IEnumerable<Type> Get(string key, bool ignoreCase)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(key))
|
|
{
|
|
return Type.EmptyTypes;
|
|
}
|
|
|
|
List<Type> t;
|
|
|
|
if (ignoreCase)
|
|
{
|
|
m_Insensitive.TryGetValue(key, out t);
|
|
}
|
|
else
|
|
{
|
|
m_Sensitive.TryGetValue(key, out t);
|
|
}
|
|
|
|
if (t == null)
|
|
{
|
|
return Type.EmptyTypes;
|
|
}
|
|
|
|
return t.AsEnumerable();
|
|
}
|
|
|
|
public TypeTable(int capacity)
|
|
{
|
|
m_Sensitive = new Dictionary<string, List<Type>>(capacity);
|
|
m_Insensitive = new Dictionary<string, List<Type>>(capacity, StringComparer.OrdinalIgnoreCase);
|
|
}
|
|
}
|
|
}
|