/// <summary> /// Spawns monster. /// </summary> /// <remarks> /// Parameters: /// - int monsterId /// - string mapName /// - float x /// - float y /// - float z /// </remarks> /// <param name="L"></param> /// <returns></returns> private int spawn(IntPtr L) { var monsterId = Melua.luaL_checkinteger(L, 1); var mapName = Melua.luaL_checkstring(L, 2); var x = (float)Melua.luaL_checknumber(L, 3); var y = (float)Melua.luaL_checknumber(L, 4); var z = (float)Melua.luaL_checknumber(L, 5); Melua.lua_pop(L, 5); var map = ChannelServer.Instance.World.GetMap(mapName); if (map == null) { return(Melua.melua_error(L, "Map '{0}' not found.", mapName)); } var monster = new Monster(monsterId, NpcType.Monster); monster.Position = new Position(x, y, z); map.AddMonster(monster); return(0); }
private int loadxmlfile(IntPtr L) { var fileName = Melua.luaL_checkstring(L, 1); Melua.lua_pop(L, 1); fileName = ModPack.NormalizePath(fileName); if (!fileName.EndsWith(".xml") && !fileName.EndsWith(".xml.compiled")) { return(Melua.melua_error(L, "Expected XML file extension.")); } if (!FileExistsInPackages(fileName)) { return(Melua.melua_error(L, "File '{0}' not found in packages.", fileName)); } try { _loadedXmlFile = fileName; _loadedXmlModder = _modPack.GetXmlModder(fileName); return(0); } catch (XmlException ex) { _loadedXmlFile = null; _loadedXmlModder = null; return(Melua.melua_error(L, "Failed to parse XML: '{0}'", ex.Message)); } }
private int addelement(IntPtr L) { if (!IsXmlFileLoaded()) { return(Melua.melua_error(L, "No XML file loaded.")); } var selector = Melua.luaL_checkstring(L, 1); var xml = Melua.luaL_checkstring(L, 2); Melua.lua_pop(L, 2); if (!IsXPathValid(selector)) { return(Melua.melua_error(L, "Invalid XPath.")); } XElement element; try { element = XElement.Parse(xml); } catch (XmlException ex) { return(Melua.melua_error(L, "Failed to parse XML element: {0}", ex.Message)); } var modder = _loadedXmlModder; _modPack.AddMod(new XmlElementAdder(modder, selector, element)); return(0); }
/// <summary> /// Removes the specified amount of items with the given id from /// character's inventory. /// </summary> /// <remarks> /// Parameters: /// - int itemId /// - int amount /// /// Result: /// - int removedCount /// </remarks> /// <param name="L"></param> /// <returns></returns> private int removeitem(IntPtr L) { var conn = this.GetConnectionFromState(L); var character = conn.SelectedCharacter; var itemId = Melua.luaL_checkinteger(L, 1); var amount = Melua.luaL_checkinteger(L, 2); Melua.lua_pop(L, 2); var itemData = ChannelServer.Instance.Data.ItemDb.Find(itemId); if (itemData == null) { return(Melua.melua_error(L, "Unknown item id.")); } amount = Math.Max(0, amount); var removed = character.Inventory.Remove(itemId, amount, InventoryItemRemoveMsg.Given); Melua.lua_pushinteger(L, removed); return(1); }
/// <summary> /// Adds the specified amount of items to the character's inventory. /// </summary> /// <remarks> /// Parameters: /// - int itemId /// - int amount /// </remarks> /// <param name="L"></param> /// <returns></returns> private int additem(IntPtr L) { var conn = this.GetConnectionFromState(L); var character = conn.SelectedCharacter; var itemId = Melua.luaL_checkinteger(L, 1); var amount = Melua.luaL_checkinteger(L, 2); Melua.lua_pop(L, 2); var itemData = ChannelServer.Instance.Data.ItemDb.Find(itemId); if (itemData == null) { return(Melua.melua_error(L, "Unknown item id.")); } try { character.Inventory.Add(itemId, amount, InventoryAddType.PickUp); } catch (Exception ex) { Log.Exception(ex); return(Melua.melua_error(L, "Failed to add item to inventory.")); } return(0); }
/// <summary> /// Removes thread and the associated state from the manager. /// </summary> /// <param name="state"></param> /// <returns></returns> public void RemoveThread(LuaThread thread) { if (thread == null || thread.L == IntPtr.Zero) { return; } lock (_glSyncLock) { // Remove thread from stack and update all stack indexes, // as the removal will shift all following elements. Melua.lua_remove(GL, thread.StackIndex); lock (_states) { foreach (var state in _states.Values) { if (state.LuaThread.StackIndex > thread.StackIndex) { state.LuaThread.StackIndex--; } } } } lock (_states) _states.Remove(thread.L); // Apparently there is no lua_closethread()? }
/// <summary> /// Loads file from given path. /// </summary> /// <param name="filePath"></param> /// <returns></returns> private bool LoadFile(string filePath) { if (!File.Exists(filePath)) { Log.Error("ScriptManager.LoadFile: File '{0}' not found.", filePath); return(false); } // Load file var result = Melua.luaL_loadfile(GL, filePath); if (result != 0) { Log.Error("ScriptManager.LoadFile: Failed to compile '{0}' (Error code: {1}).\n{2}", filePath, result, Melua.lua_tostring(GL, -1)); return(false); } // Execute it if (Melua.lua_pcall(GL, 0, 0, 0) != 0) { Log.Error("ScriptManager.LoadFile: Failed to load '{0}'.\n{1}", filePath, Melua.lua_tostring(GL, -1)); return(false); } return(true); }
/// <summary> /// Calls function with connection's script state. /// </summary> /// <param name="conn"></param> /// <param name="functionName"></param> public void Call(ChannelConnection conn, string functionName) { var NL = conn.ScriptState.NL; Melua.lua_getglobal(NL, functionName); if (Melua.lua_isnil(NL, -1)) { Log.Error("ScriptManager.Call: Function '{0}' not found.", functionName); return; } 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.CurrentNpc = null; } }
/// <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> /// Pushes the arguments onto the Lua stack. /// </summary> /// <param name="NL"></param> /// <param name="args"></param> private static void PushArguments(IntPtr NL, params object[] args) { foreach (var arg in args) { switch (arg) { case byte v: Melua.lua_pushinteger(NL, v); break; case bool v: Melua.lua_pushboolean(NL, v); break; case short v: Melua.lua_pushinteger(NL, v); break; case int v: Melua.lua_pushinteger(NL, v); break; case float v: Melua.lua_pushnumber(NL, v); break; case double v: Melua.lua_pushnumber(NL, v); break; case string v: Melua.lua_pushstring(NL, v); break; default: { Log.Warning("ScriptManager.PushArguments: Invalid argument type '{0}', pushing 'int 0' instead.", arg.GetType().Name); Melua.lua_pushinteger(NL, 0); break; } } } }
public bool LoadMods(string modPath) { var errors = false; var mainModFiles = Directory.EnumerateFiles(modPath, "main.lua", SearchOption.AllDirectories); if (!mainModFiles.Any()) { return(false); } Trace.WriteLine("Mods found: " + mainModFiles.Count()); foreach (var filePath in mainModFiles) { var folderPath = (Path.GetDirectoryName(filePath)); var folderName = Path.GetFileName(folderPath); _cwd = Path.GetFullPath(folderPath); this.ModCount++; Trace.WriteLine(string.Format("Loading '{0}'...", folderName)); if (Melua.luaL_dofile(L, filePath) != 0) { Trace.WriteLine(string.Format("Error in {1}", folderName, Melua.lua_tostring(L, -1))); errors = true; } } return(errors); }
/// <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)); }
private int include(IntPtr L) { var path = Melua.luaL_checkstring(L, 1); Melua.lua_pop(L, 1); return(this.DoPath(L, path, false)); }
/// <summary> /// Registers function on global Lua state. /// </summary> /// <param name="functionName"></param> private void Register(Melua.LuaNativeFunction function) { // Keep a reference, so it's not garbage collected...? var func = new Melua.LuaNativeFunction(function); _functions.Add(func); Melua.lua_register(GL, function.Method.Name, func); }
private int require(IntPtr L) { var path = Melua.luaL_checkstring(L, 1); Melua.lua_pop(L, 1); return(this.DoPath(L, path, true)); }
/// <summary> /// Returns a script state for the connection. /// </summary> /// <param name="conn"></param> /// <returns></returns> public ScriptState CreateScriptState(ChannelConnection conn) { var NL = Melua.lua_newthread(GL); var state = new ScriptState(conn, NL); lock (_states) _states.Add(NL, state); return(state); }
//-----------------------------------------------------------------// // SCRIPT FUNCTIONS // //-----------------------------------------------------------------// /// <summary> /// Prints message in console using Console.WriteLine. /// </summary> /// <remarks> /// Parameters: /// - string message /// </remarks> /// <param name="L"></param> /// <returns></returns> private int print(IntPtr L) { var msg = Melua.luaL_checkstring(L, 1); Melua.lua_pop(L, 1); Console.WriteLine(msg); return(0); }
//-----------------------------------------------------------------// // SCRIPT FUNCTIONS // //-----------------------------------------------------------------// /// <summary> /// Prints and logs debug message. /// </summary> /// <remarks> /// Parameters: /// - string message /// </remarks> /// <param name="L"></param> /// <returns></returns> private int logdebug(IntPtr L) { var msg = Melua.luaL_checkstring(L, 1); Melua.lua_pop(L, 1); Log.Debug(msg); return(0); }
/// <summary> /// Returns table, containing information about the current date/time. /// </summary> /// <remarks> /// Result: /// { /// integer year, -- Current year /// integer month, -- Current month /// integer day, -- Current day /// integer weekday, -- Day of the week (0-6), starting on Sunday /// integer yearday, -- Day of the current year /// integer hour, -- Current hours (0-23) /// integer min, -- Current minutes (0-59) /// integer sec, -- Current seconds (0-59) /// integer msec, -- Current milliseconds (0-999) /// boolean isdst, -- Is Daylight Saving Time? /// integer unixts, -- Unix timestamp /// } /// </remarks> /// <param name="L"></param> /// <returns></returns> private int gettime(IntPtr L) { var now = DateTime.Now; // TODO: Could a general table generation like this be cached? Melua.lua_newtable(L); Melua.lua_pushstring(L, "year"); Melua.lua_pushinteger(L, now.Year); Melua.lua_settable(L, -3); Melua.lua_pushstring(L, "month"); Melua.lua_pushinteger(L, now.Month); Melua.lua_settable(L, -3); Melua.lua_pushstring(L, "day"); Melua.lua_pushinteger(L, now.Day); Melua.lua_settable(L, -3); Melua.lua_pushstring(L, "weekday"); Melua.lua_pushinteger(L, (int)now.DayOfWeek); Melua.lua_settable(L, -3); Melua.lua_pushstring(L, "yearday"); Melua.lua_pushinteger(L, now.DayOfYear); Melua.lua_settable(L, -3); Melua.lua_pushstring(L, "hour"); Melua.lua_pushinteger(L, now.Hour); Melua.lua_settable(L, -3); Melua.lua_pushstring(L, "min"); Melua.lua_pushinteger(L, now.Minute); Melua.lua_settable(L, -3); Melua.lua_pushstring(L, "sec"); Melua.lua_pushinteger(L, now.Second); Melua.lua_settable(L, -3); Melua.lua_pushstring(L, "msec"); Melua.lua_pushinteger(L, now.Millisecond); Melua.lua_settable(L, -3); Melua.lua_pushstring(L, "isdst"); Melua.lua_pushboolean(L, now.IsDaylightSavingTime()); Melua.lua_settable(L, -3); Melua.lua_pushstring(L, "unixts"); Melua.lua_pushinteger(L, (int)(now.ToUniversalTime().Subtract(UnixEpoch)).TotalSeconds); Melua.lua_settable(L, -3); return(1); }
/// <summary> /// Called if Lua panics. /// </summary> /// <param name="L"></param> /// <returns></returns> private int OnPanic(IntPtr L) { var error = string.Format("unprotected error in call to Lua API ({0})", Melua.lua_tostring(L, -1)); // Throwing an exception to get out of here, which should stop // Lua from exiting the host process, causes a stack overflow // for some reason. //throw new Exception("Lua panic: " + error); Log.Error("Lua panic: " + error); return(0); }
public void type() { var L = Melua.luaL_newstate(); Melua.lua_pushinteger(L, 123); Assert.Equal(Melua.LUA_TNUMBER, Melua.lua_type(L, -1)); Melua.lua_pushstring(L, "123"); Assert.Equal(Melua.LUA_TSTRING, Melua.lua_type(L, -1)); Melua.lua_newtable(L); Assert.Equal(Melua.LUA_TTABLE, Melua.lua_type(L, -1)); }
public void typename() { var L = Melua.luaL_newstate(); Melua.lua_pushinteger(L, 123); Assert.Equal("number", Melua.luaL_typename(L, -1)); Melua.lua_pushstring(L, "123"); Assert.Equal("string", Melua.luaL_typename(L, -1)); Melua.lua_newtable(L); Assert.Equal("table", Melua.luaL_typename(L, -1)); }
public void tonumber() { var L = Melua.luaL_newstate(); Melua.lua_pushstring(L, "123"); Assert.Equal(123, Melua.lua_tonumber(L, -1)); Melua.lua_pushstring(L, "0x123"); Assert.Equal(0x123, Melua.lua_tonumber(L, -1)); Melua.lua_pushstring(L, "0x12AB34"); Assert.Equal(0x12AB34, Melua.lua_tonumber(L, -1)); }
private int removeelements(IntPtr L) { if (!IsXmlFileLoaded()) { return(Melua.melua_error(L, "No XML file loaded.")); } var selectors = new HashSet <string>(); if (Melua.lua_isstring(L, -1)) { var selector = Melua.luaL_checkstring(L, 1); Melua.lua_pop(L, 1); if (!IsXPathValid(selector)) { return(Melua.melua_error(L, "Invalid XPath.")); } selectors.Add(selector); } else if (Melua.lua_istable(L, -1)) { Melua.lua_pushnil(L); while (Melua.lua_next(L, -2) != 0) { var selector = Melua.luaL_checkstring(L, -1); Melua.lua_pop(L, 1); if (!IsXPathValid(selector)) { return(Melua.melua_error(L, "Invalid XPath.")); } selectors.Add(selector); } } else { return(Melua.melua_error(L, "Invalid argument type '{0}'.", Melua.luaL_typename(L, -1))); } var modder = _loadedXmlModder; foreach (var selector in selectors) { _modPack.AddMod(new XmlElementRemover(modder, selector)); } return(0); }
/// <summary> /// Resumes dialog script after yielding. /// </summary> /// <param name="conn"></param> /// <param name="argument"></param> public void ResumeDialog(ChannelConnection conn, params object[] arguments) { if (conn.ScriptState.LuaThread == null) { Send.ZC_DIALOG_CLOSE(conn); Log.Warning("ScriptManager: Resume on empty ScriptState from user '{0}'.", conn.Account.Name); return; } var NL = conn.ScriptState.LuaThread.L; var argc = arguments?.Length ?? 0; // Reset current shop in case we came from one. conn.ScriptState.CurrentShop = null; if (argc != 0) { PushArguments(NL, arguments); // If arguments were passed, we can assume we're coming from // a selection handler, which's windows don't disappear when // sending the next message. So let's close it before // continuing. Send.ZC_DIALOG_CLOSE(conn); } var result = Melua.lua_resume(NL, argc); // Log error if result is not success or yield if (result != 0 && result != Melua.LUA_YIELD) { Log.Error("ScriptManager.Call: Error while resuming script for {0}.\n{1}", 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) { // Only close from here if the end was reached after an // argument-less resume, since close is already called // from the argument handling to get rid of the selection // dialog. if (argc == 0) { Send.ZC_DIALOG_CLOSE(conn); } conn.ScriptState.Reset(); } }
public void tocfunction() { var L = Melua.luaL_newstate(); var n = 0; Melua.melua_register(L, "foo", _ => { n += 1; return(0); }); Melua.melua_register(L, "bar", _ => { var func = Melua.lua_tocfunction(L, 1); func(L); return(0); }); Melua.luaL_dostring(L, "foo()"); Assert.Equal(1, n); Melua.luaL_dostring(L, "bar(foo)"); Assert.Equal(2, n); }
/// <summary> /// Changes the player's hairstyle. /// </summary> /// <remarks> /// Parameters: /// - int hairId /// </remarks> /// <param name="L"></param> /// <returns></returns> private int changehair(IntPtr L) { var conn = this.GetConnectionFromState(L); var character = conn.SelectedCharacter; var hairId = Melua.luaL_checkinteger(L, 1); Melua.lua_pop(L, 1); character.Hair = (byte)hairId; Send.ZC_UPDATED_PCAPPEARANCE(character); return(0); }
/// <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> /// Returns a table with information about the player. /// </summary> /// <remarks> /// Result: /// { /// string name, -- Character's name /// string teamName, -- Character's team name /// integer gender, -- Character's gender /// integer level, -- Character's level /// integer hp, -- Character's HP /// integer maxHp, -- Character's max HP /// integer sp, -- Character's SP /// integer maxSp, -- Character's max SP /// integer stamina, -- Character's stamina /// integer hair, -- Character's hair /// } /// </remarks> /// <param name="L"></param> /// <returns></returns> private int getpc(IntPtr L) { var conn = this.GetConnectionFromState(L); var character = conn.SelectedCharacter; Melua.lua_newtable(L); Melua.lua_pushstring(L, "name"); Melua.lua_pushstring(L, character.Name); Melua.lua_settable(L, -3); Melua.lua_pushstring(L, "teamName"); Melua.lua_pushstring(L, character.TeamName); Melua.lua_settable(L, -3); Melua.lua_pushstring(L, "gender"); Melua.lua_pushinteger(L, (int)character.Gender); Melua.lua_settable(L, -3); Melua.lua_pushstring(L, "level"); Melua.lua_pushinteger(L, character.Level); Melua.lua_settable(L, -3); Melua.lua_pushstring(L, "hp"); Melua.lua_pushinteger(L, character.Hp); Melua.lua_settable(L, -3); Melua.lua_pushstring(L, "maxHp"); Melua.lua_pushinteger(L, character.MaxHp); Melua.lua_settable(L, -3); Melua.lua_pushstring(L, "sp"); Melua.lua_pushinteger(L, character.Sp); Melua.lua_settable(L, -3); Melua.lua_pushstring(L, "maxSp"); Melua.lua_pushinteger(L, character.MaxSp); Melua.lua_settable(L, -3); Melua.lua_pushstring(L, "stamina"); Melua.lua_pushinteger(L, character.Stamina); Melua.lua_settable(L, -3); Melua.lua_pushstring(L, "hair"); Melua.lua_pushinteger(L, character.Hair); Melua.lua_settable(L, -3); return(1); }