/// <summary> /// Sends dialog numberrange message, showing a message and a small text field, /// for the user to put in a number. /// </summary> /// <remarks> /// NUMBERRANGE uses CZ_DIALOG_SELECT for its response, /// which means the number range is that of a byte, 0~255. /// /// Parameters: /// - string message /// - int min (optional, defaults to 0) /// - int max (optional, defaults to 255) /// /// Result: /// The number put in by the user. /// Returns 0 on error. /// </remarks> /// <param name="L"></param> /// <returns></returns> private int numinput(IntPtr L) { // Check arguments and return 0 on error var argc = Melua.lua_gettop(L); if (argc == 0) { Log.Warning("numinput: No arguments."); Melua.lua_pushinteger(L, 0); return(1); } var conn = this.GetConnectionFromState(L); int min = 0, max = 255; // Get arguments var msg = Melua.luaL_checkstring(L, 1); if (argc >= 3) { min = Melua.luaL_checkinteger(L, 2); max = Melua.luaL_checkinteger(L, 3); } Melua.lua_pop(L, argc); this.HandleCustomCode(conn, ref msg); this.AttachNpcName(conn, ref msg); Send.ZC_DIALOG_NUMBERRANGE(conn, msg, min, max); return(Melua.lua_yield(L, 1)); }
/// <summary> /// Sends dialog select message to client, showing a message and a /// list of options to select from. /// </summary> /// <remarks> /// Select can take an arbitrary amount of options. /// /// Parameters: /// - string message /// - string options... /// /// Result: /// The number of the selected option, starting from 1. /// Returns 0 on error. /// </remarks> /// <param name="L"></param> /// <returns></returns> private int select(IntPtr L) { // Check arguments and return 0 on error var argc = Melua.lua_gettop(L); if (argc == 0) { Log.Warning("select: No arguments."); Melua.lua_pushinteger(L, 0); return(1); } var conn = this.GetConnectionFromState(L); // Get arguments, first argument is the message, everything afterwards // is one option to select from. var args = new string[argc]; for (int i = 1; i <= argc; ++i) { var arg = Melua.luaL_checkstring(L, i); this.HandleCustomCode(conn, ref arg); args[i - 1] = arg; } Melua.lua_pop(L, argc); this.AttachNpcName(conn, ref args[0]); Send.ZC_DIALOG_SELECT(conn, args); return(Melua.lua_yield(L, 1)); }
/// <summary> /// Sends dialog input message, showing a message and a text field, /// for the user to put in a string. /// </summary> /// <remarks> /// Parameters: /// - string message /// /// Result: /// The string put in by the user. /// Returns empty string on error. /// </remarks> /// <param name="L"></param> /// <returns></returns> private int input(IntPtr L) { // Check arguments and return empty string on error var argc = Melua.lua_gettop(L); if (argc == 0) { Log.Warning("input: No arguments."); Melua.lua_pushstring(L, ""); return(1); } var conn = this.GetConnectionFromState(L); // Get message var msg = Melua.luaL_checkstring(L, 1); Melua.lua_pop(L, 1); this.HandleCustomCode(conn, ref msg); this.AttachNpcName(conn, ref msg); Send.ZC_DIALOG_STRINGINPUT(conn, msg); return(Melua.lua_yield(L, 1)); }
/// <summary> /// Calls function with connection's script state. /// </summary> /// <param name="conn"></param> /// <param name="functionName"></param> /// <param name="arguments"></param> public ScriptCallResult Call(ChannelConnection conn, string functionName, params object[] arguments) { if (conn.ScriptState.LuaThread != null) { Log.Warning("ScriptManager.Call: A previous thread wasn't closed for user '{0}'.", conn.Account.Name); } // Prepare thread conn.ScriptState.LuaThread = this.GetNewThread(conn.ScriptState); var NL = conn.ScriptState.LuaThread.L; var top = Melua.lua_gettop(_gL); // Get function Melua.lua_getglobal(NL, functionName); if (Melua.lua_isnil(NL, -1)) { conn.ScriptState.Reset(); return(new ScriptCallResult(ScriptCallResultType.NotFound)); } // Push all arguments var argc = arguments?.Length ?? 0; if (argc != 0) { PushArguments(NL, arguments); } // Execute the function with the arguments var funcResult = Melua.lua_resume(NL, argc); // If result is not a success or yield, an error occurred. if (funcResult != 0 && funcResult != Melua.LUA_YIELD) { conn.ScriptState.Reset(); var errorMessage = string.Format("Error while executing '{0}' for user '{1}': {2}", functionName, conn.Account.Name, Melua.lua_tostring(NL, -1)); return(new ScriptCallResult(ScriptCallResultType.Error, errorMessage)); } // We currently don't expect functions called this way to yield if (funcResult == Melua.LUA_YIELD) { Log.Warning("ScriptManager.Call: Script function '{0}' yielded for user '{1}', a behavior that's currently not handled.", functionName, conn.Account.Name); } conn.ScriptState.Reset(); return(new ScriptCallResult(ScriptCallResultType.Success)); }
/// <summary> /// Calls function with connection's script state. /// </summary> /// <param name="conn"></param> /// <param name="functionName"></param> public void Call(ChannelConnection conn, string functionName) { if (conn.ScriptState.LuaThread != null) { Log.Warning("ScriptManager.Call: A previous thread wasn't closed for user '{0}'.", conn.Account.Name); } // Get function name, use oneliner for localized, single-line // dialogues. if (this.IsOneLiner(functionName)) { functionName = "npc_oneliner"; } // Prepare thread conn.ScriptState.LuaThread = this.GetNewThread(conn.ScriptState); var NL = conn.ScriptState.LuaThread.L; var top = Melua.lua_gettop(GL); // Get function Melua.lua_getglobal(NL, functionName); if (Melua.lua_isnil(NL, -1)) { Log.Error("ScriptManager.Call: Function '{0}' not found.", functionName); return; } // Run var result = Melua.lua_resume(NL, 0); // Log error if result is not success or yield if (result != 0 && result != Melua.LUA_YIELD) { Log.Error("ScriptManager.Call: Error while executing '{0}' for {1}.\n{2}", functionName, conn.Account.Name, Melua.lua_tostring(NL, -1)); result = 0; // Set to 0 to close dialog on error } // Close dialog if end of function was reached if (result == 0) { Send.ZC_DIALOG_CLOSE(conn); conn.ScriptState.Reset(); } }
/// <summary> /// Creates new Lua thread for the state to use and saves /// a reference to the connection's script state. /// </summary> /// <param name="conn"></param> /// <returns></returns> public LuaThread GetNewThread(ScriptState state) { if (state.LuaThread != null) { this.RemoveThread(state.LuaThread); } IntPtr NL; int index; lock (_glSyncLock) { // Create new thread and save index, so it can be removed later. NL = Melua.lua_newthread(GL); index = Melua.lua_gettop(GL); } lock (_states) _states.Add(NL, state); return(new LuaThread(NL, index)); }
/// <summary> /// Gets or sets a scripting variable. /// </summary> /// <remarks> /// Scripting variables are separate from Lua variables and exist /// across script and playing sessions. How the variable is saved /// depends on the used prefix. /// /// Variable names may contain the following characters, apart from /// the prefixes, and must start with a character: /// abcdefghijklmnopqrstuvwxyz0123456789_ /// /// Prefixes: /// "" - Permanent variable attached to the character. /// "@" - Temporary variable attached to the character. /// "#" - Permanent variable attached to the account. /// "$" - Permanent global variable. /// "$@" - Temporary global variable. /// /// Parameters: /// - string variableName /// - (optional) T value /// /// Result: /// - T value /// </remarks> /// <param name="L"></param> /// <returns></returns> private int var(IntPtr L) { var conn = this.GetConnectionFromState(L); var character = conn.SelectedCharacter; // Get parameters var argc = Melua.lua_gettop(L); var name = Melua.luaL_checkstring(L, 1).Trim(); object value = null; if (argc == 2) { if (Melua.lua_isnumber(L, 2)) { value = Melua.lua_tonumber(L, 2); } else if (Melua.lua_isstring(L, 2)) { value = Melua.lua_tostring(L, 2); } else if (Melua.lua_isboolean(L, 2)) { value = Melua.lua_toboolean(L, 2); } else { return(Melua.melua_error(L, "Unsupported variable type.")); } } Melua.lua_pop(L, argc); // Get variable manager and trim name VariableManager vars; if (name.StartsWith("$@")) { vars = this.Variables.Temp; name = name.Substring(2); } else if (name.StartsWith("$")) { vars = this.Variables.Perm; name = name.Substring(1); } else if (name.StartsWith("#")) { vars = conn.Account.Variables.Perm; name = name.Substring(1); } else if (name.StartsWith("@")) { vars = character.Variables.Temp; name = name.Substring(1); } else { vars = character.Variables.Perm; } // Check name syntax, if we want to add more prefixes later on, // we can't have special characters in names. if (!VarNameCheck.IsMatch(name)) { return(Melua.melua_error(L, "Invalid variable name.")); } // Update or get value if (value == null) { value = vars[name]; } else { vars[name] = value; } // Push return value if (value == null) { Melua.lua_pushnil(L); } else if (value is string) { Melua.lua_pushstring(L, (string)value); } else if (value is double) { Melua.lua_pushnumber(L, (double)value); } else if (value is float) { Melua.lua_pushnumber(L, (float)value); } else if (value is int) { Melua.lua_pushinteger(L, (int)value); } else if (value is bool) { Melua.lua_pushboolean(L, (bool)value); } else { return(Melua.melua_error(L, "Unsupported variable type '{0}'.", value.GetType().Name)); } return(1); }
private int DoPath(IntPtr L, string path, bool errorOnMissing) { var fullPath = path; var isHttp = (path.StartsWith("http://") || path.StartsWith("https://")); var status = 0; if (isHttp) { try { var wc = new WebClient(); var script = wc.DownloadString(fullPath); status = Melua.luaL_dostring(L, script); } catch (WebException ex) { if (errorOnMissing) { return(Melua.melua_error(L, "Failed to include remote script '{0}' ({1}).", fullPath, ex.Message)); } } } else { fullPath = Path.GetFullPath(Path.Combine(_cwd, path)); if (!IsInsideCwd(fullPath)) { return(Melua.melua_error(L, "Invalid path. ({0})", path)); } if (!File.Exists(fullPath)) { if (errorOnMissing) { return(Melua.melua_error(L, "File not found. ({0})", path)); } else { return(0); } } status = Melua.luaL_dofile(L, fullPath); } if (status == 1) // Error in do/load { return(Melua.melua_error(L, Melua.lua_tostring(L, -1))); } if (status != 0) // Error in execution { return(status); } var returnValues = Melua.lua_gettop(L); return(returnValues); }