Skip to content

Commit 7ad0ae5

Browse files
committed
Merge pull request #132 from ORelio/Indev
Merge changes for v1.9.0 BETA
2 parents 5b43056 + e809720 commit 7ad0ae5

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

72 files changed

+8334
-2136
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,4 @@
33
/MinecraftClientGUI.v11.suo
44
/MinecraftClientGUI.suo
55
/MinecraftClient.userprefs
6+
/.vs/

MinecraftClient/CSharpRunner.cs

Lines changed: 374 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,374 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Text;
5+
using System.IO;
6+
using Microsoft.CSharp;
7+
using System.CodeDom.Compiler;
8+
using System.Reflection;
9+
using System.Threading;
10+
11+
namespace MinecraftClient
12+
{
13+
/// <summary>
14+
/// C# Script runner - Compile on-the-fly and run C# scripts
15+
/// </summary>
16+
class CSharpRunner
17+
{
18+
private static readonly Dictionary<ulong, Assembly> CompileCache = new Dictionary<ulong, Assembly>();
19+
20+
/// <summary>
21+
/// Run the specified C# script file
22+
/// </summary>
23+
/// <param name="apiHandler">ChatBot handler for accessing ChatBot API</param>
24+
/// <param name="tickHandler">Tick handler for waiting after some API calls</param>
25+
/// <param name="lines">Lines of the script file to run</param>
26+
/// <param name="args">Arguments to pass to the script</param>
27+
/// <param name="run">Set to false to compile and cache the script without launching it</param>
28+
/// <exception cref="CSharpException">Thrown if an error occured</exception>
29+
/// <returns>Result of the execution, returned by the script</returns>
30+
public static object Run(ChatBot apiHandler, ManualResetEvent tickHandler, string[] lines, string[] args, bool run = true)
31+
{
32+
//Script compatibility check for handling future versions differently
33+
if (lines.Length < 1 || lines[0] != "//MCCScript 1.0")
34+
throw new CSharpException(CSErrorType.InvalidScript,
35+
new InvalidDataException("The provided script does not have a valid MCCScript header"));
36+
37+
//Script hash for determining if it was previously compiled
38+
ulong scriptHash = QuickHash(lines);
39+
Assembly assembly = null;
40+
41+
//No need to compile two scripts at the same time
42+
lock (CompileCache)
43+
{
44+
///Process and compile script only if not already compiled
45+
if (!Settings.CacheScripts || !CompileCache.ContainsKey(scriptHash))
46+
{
47+
//Process different sections of the script file
48+
bool scriptMain = true;
49+
List<string> script = new List<string>();
50+
List<string> extensions = new List<string>();
51+
foreach (string line in lines)
52+
{
53+
if (line.StartsWith("//MCCScript"))
54+
{
55+
if (line.EndsWith("Extensions"))
56+
scriptMain = false;
57+
}
58+
else if (scriptMain)
59+
script.Add(line);
60+
else extensions.Add(line);
61+
}
62+
63+
//Add return statement if missing
64+
if (script.All(line => !line.StartsWith("return ") && !line.Contains(" return ")))
65+
script.Add("return null;");
66+
67+
//Generate a class from the given script
68+
string code = String.Join("\n", new string[]
69+
{
70+
"using System;",
71+
"using System.Collections.Generic;",
72+
"using System.Text.RegularExpressions;",
73+
"using System.Linq;",
74+
"using System.Text;",
75+
"using System.IO;",
76+
"using System.Threading;",
77+
"using MinecraftClient;",
78+
"using MinecraftClient.Mapping;",
79+
"namespace ScriptLoader {",
80+
"public class Script {",
81+
"public CSharpAPI MCC;",
82+
"public object __run(CSharpAPI __apiHandler, string[] args) {",
83+
"this.MCC = __apiHandler;",
84+
String.Join("\n", script),
85+
"}",
86+
String.Join("\n", extensions),
87+
"}}",
88+
});
89+
90+
//Compile the C# class in memory using all the currently loaded assemblies
91+
CSharpCodeProvider compiler = new CSharpCodeProvider();
92+
CompilerParameters parameters = new CompilerParameters();
93+
parameters.ReferencedAssemblies
94+
.AddRange(AppDomain.CurrentDomain
95+
.GetAssemblies()
96+
.Where(a => !a.IsDynamic)
97+
.Select(a => a.Location).ToArray());
98+
parameters.CompilerOptions = "/t:library";
99+
parameters.GenerateInMemory = true;
100+
CompilerResults result = compiler.CompileAssemblyFromSource(parameters, code);
101+
102+
//Process compile warnings and errors
103+
if (result.Errors.Count > 0)
104+
throw new CSharpException(CSErrorType.LoadError,
105+
new InvalidOperationException(result.Errors[0].ErrorText));
106+
107+
//Retrieve compiled assembly
108+
assembly = result.CompiledAssembly;
109+
if (Settings.CacheScripts)
110+
CompileCache[scriptHash] = result.CompiledAssembly;
111+
}
112+
else if (Settings.CacheScripts)
113+
assembly = CompileCache[scriptHash];
114+
}
115+
116+
//Run the compiled assembly with exception handling
117+
if (run)
118+
{
119+
try
120+
{
121+
object compiledScript
122+
= CompileCache[scriptHash].CreateInstance("ScriptLoader.Script");
123+
return
124+
compiledScript
125+
.GetType()
126+
.GetMethod("__run")
127+
.Invoke(compiledScript,
128+
new object[] { new CSharpAPI(apiHandler, tickHandler), args });
129+
}
130+
catch (Exception e) { throw new CSharpException(CSErrorType.RuntimeError, e); }
131+
}
132+
else return null;
133+
}
134+
135+
/// <summary>
136+
/// Quickly calculate a hash for the given script
137+
/// </summary>
138+
/// <param name="lines">script lines</param>
139+
/// <returns>Quick hash as unsigned long</returns>
140+
private static ulong QuickHash(string[] lines)
141+
{
142+
ulong hashedValue = 3074457345618258791ul;
143+
for (int i = 0; i < lines.Length; i++)
144+
{
145+
for (int j = 0; j < lines[i].Length; j++)
146+
{
147+
hashedValue += lines[i][j];
148+
hashedValue *= 3074457345618258799ul;
149+
}
150+
hashedValue += '\n';
151+
hashedValue *= 3074457345618258799ul;
152+
}
153+
return hashedValue;
154+
}
155+
}
156+
157+
/// <summary>
158+
/// Describe a C# script error type
159+
/// </summary>
160+
public enum CSErrorType { FileReadError, InvalidScript, LoadError, RuntimeError };
161+
162+
/// <summary>
163+
/// Describe a C# script error with associated error type
164+
/// </summary>
165+
public class CSharpException : Exception
166+
{
167+
private CSErrorType _type;
168+
public CSErrorType ExceptionType { get { return _type; } }
169+
public override string Message { get { return InnerException.Message; } }
170+
public override string ToString() { return InnerException.ToString(); }
171+
public CSharpException(CSErrorType type, Exception inner)
172+
: base(inner != null ? inner.Message : "", inner)
173+
{
174+
_type = type;
175+
}
176+
}
177+
178+
/// <summary>
179+
/// Represents the C# API object accessible from C# Scripts
180+
/// </summary>
181+
public class CSharpAPI : ChatBot
182+
{
183+
/// <summary>
184+
/// Thread blocking utility for stopping execution when making a ChatBot API call
185+
/// </summary>
186+
private ManualResetEvent tickHandler;
187+
188+
/// <summary>
189+
/// Create a new C# API Wrapper
190+
/// </summary>
191+
/// <param name="apiHandler">ChatBot API Handler</param>
192+
/// <param name="tickHandler">ChatBot tick handler</param>
193+
public CSharpAPI(ChatBot apiHandler, ManualResetEvent tickHandler)
194+
{
195+
SetMaster(apiHandler);
196+
this.tickHandler = tickHandler;
197+
}
198+
199+
/* == Wrappers for ChatBot API with public visibility and call limit to one per tick for safety == */
200+
201+
/// <summary>
202+
/// Write some text in the console. Nothing will be sent to the server.
203+
/// </summary>
204+
/// <param name="text">Log text to write</param>
205+
new public void LogToConsole(object text)
206+
{
207+
base.LogToConsole(text);
208+
}
209+
210+
/// <summary>
211+
/// Send text to the server. Can be anything such as chat messages or commands
212+
/// </summary>
213+
/// <param name="text">Text to send to the server</param>
214+
/// <returns>True if the text was sent with no error</returns>
215+
public bool SendText(object text)
216+
{
217+
bool result = base.SendText(text is string ? (string)text : text.ToString());
218+
tickHandler.WaitOne();
219+
Thread.Sleep(1000);
220+
return result;
221+
}
222+
223+
/// <summary>
224+
/// Perform an internal MCC command (not a server command, use SendText() instead for that!)
225+
/// </summary>
226+
/// <param name="command">The command to process</param>
227+
/// <returns>TRUE if the command was indeed an internal MCC command</returns>
228+
new public bool PerformInternalCommand(string command)
229+
{
230+
bool result = base.PerformInternalCommand(command);
231+
tickHandler.WaitOne();
232+
return result;
233+
}
234+
235+
/// <summary>
236+
/// Disconnect from the server and restart the program
237+
/// It will unload and reload all the bots and then reconnect to the server
238+
/// </summary>
239+
/// <param name="extraAttempts">If connection fails, the client will make X extra attempts</param>
240+
new public void ReconnectToTheServer(int extraAttempts = -999999)
241+
{
242+
if (extraAttempts == -999999)
243+
base.ReconnectToTheServer();
244+
else base.ReconnectToTheServer(extraAttempts);
245+
tickHandler.WaitOne();
246+
}
247+
248+
/// <summary>
249+
/// Disconnect from the server and exit the program
250+
/// </summary>
251+
new public void DisconnectAndExit()
252+
{
253+
base.DisconnectAndExit();
254+
tickHandler.WaitOne();
255+
}
256+
257+
/// <summary>
258+
/// Load the provided ChatBot object
259+
/// </summary>
260+
/// <param name="bot">Bot to load</param>
261+
new public void LoadBot(ChatBot bot)
262+
{
263+
base.LoadBot(bot);
264+
tickHandler.WaitOne();
265+
}
266+
267+
/* == Additional Methods useful for Script API == */
268+
269+
/// <summary>
270+
/// Get a global variable by name
271+
/// </summary>
272+
/// <param name="varName">Name of the variable</param>
273+
/// <returns>Value of the variable or null if no variable</returns>
274+
public object GetVar(string varName)
275+
{
276+
return Settings.GetVar(varName);
277+
}
278+
279+
/// <summary>
280+
/// Get a global variable by name, as a string
281+
/// </summary>
282+
/// <param name="varName">Name of the variable</param>
283+
/// <returns>Value of the variable as string, or null if no variable</returns>
284+
public string GetVarAsString(string varName)
285+
{
286+
object val = GetVar(varName);
287+
if (val != null)
288+
return val.ToString();
289+
return null;
290+
}
291+
292+
/// <summary>
293+
/// Get a global variable by name, as an integer
294+
/// </summary>
295+
/// <param name="varName">Name of the variable</param>
296+
/// <returns>Value of the variable as int, or 0 if no variable or not a number</returns>
297+
public int GetVarAsInt(string varName)
298+
{
299+
if (GetVar(varName) is int)
300+
return (int)GetVar(varName);
301+
int result;
302+
if (int.TryParse(GetVarAsString(varName), out result))
303+
return result;
304+
return 0;
305+
}
306+
307+
/// <summary>
308+
/// Get a global variable by name, as a boolean
309+
/// </summary>
310+
/// <param name="varName">Name of the variable</param>
311+
/// <returns>Value of the variable as bool, or false if no variable or not a boolean</returns>
312+
public bool GetVarAsBool(string varName)
313+
{
314+
if (GetVar(varName) is bool)
315+
return (bool)GetVar(varName);
316+
bool result;
317+
if (bool.TryParse(GetVarAsString(varName), out result))
318+
return result;
319+
return false;
320+
}
321+
322+
/// <summary>
323+
/// Set a global variable for further use in any other script
324+
/// </summary>
325+
/// <param name="varName">Name of the variable</param>
326+
/// <param name="varValue">Value of the variable</param>
327+
public bool SetVar(string varName, object varValue)
328+
{
329+
return Settings.SetVar(varName, varValue);
330+
}
331+
332+
/// <summary>
333+
/// Load login/password using an account alias and optionally reconnect to the server
334+
/// </summary>
335+
/// <param name="accountAlias">Account alias</param>
336+
/// <param name="andReconnect">Set to true to reconnecto to the server afterwards</param>
337+
/// <returns>True if the account was found and loaded</returns>
338+
public bool SetAccount(string accountAlias, bool andReconnect = false)
339+
{
340+
bool result = Settings.SetAccount(accountAlias);
341+
if (result && andReconnect)
342+
ReconnectToTheServer();
343+
return result;
344+
}
345+
346+
/// <summary>
347+
/// Load new server information and optionally reconnect to the server
348+
/// </summary>
349+
/// <param name="server">"serverip:port" couple or server alias</param>
350+
/// <returns>True if the server IP was valid and loaded, false otherwise</returns>
351+
public bool SetServer(string server, bool andReconnect = false)
352+
{
353+
bool result = Settings.SetServerIP(server);
354+
if (result && andReconnect)
355+
ReconnectToTheServer();
356+
return result;
357+
}
358+
359+
/// <summary>
360+
/// Synchronously call another script and retrieve the result
361+
/// </summary>
362+
/// <param name="script">Script to call</param>
363+
/// <param name="args">Arguments to pass to the script</param>
364+
/// <returns>An object returned by the script, or null</returns>
365+
public object CallScript(string script, string[] args)
366+
{
367+
string[] lines = null;
368+
ChatBots.Script.LookForScript(ref script);
369+
try { lines = File.ReadAllLines(script); }
370+
catch (Exception e) { throw new CSharpException(CSErrorType.FileReadError, e); }
371+
return CSharpRunner.Run(this, tickHandler, lines, args);
372+
}
373+
}
374+
}

0 commit comments

Comments
 (0)