diff --git a/data/lua/core/mathx.lua b/data/lua/core/mathx.lua new file mode 100644 index 000000000000..8d4315214393 --- /dev/null +++ b/data/lua/core/mathx.lua @@ -0,0 +1,99 @@ +--[========[Additional mathematical functions]========] +print("Loading mathx module...") + +function mathx.random_choice(possible_values, random_func) + random_func = random_func or mathx.random + assert(type(possible_values) == "table" or type(possible_values) == "string", + string.format("mathx.random_choice expects a string or table as parameter, got %s instead", + type(possible_values))) + + local items = {} + local num_choices = 0 + + if type(possible_values) == "string" then + -- split on commas + for word in possible_values:split() do + -- does the word contain two dots? If yes, that's a range + local dots_start, dots_end = word:find("%.%.") + if dots_start then + -- split on the dots if so and cast to numbers + local low = tonumber(word:sub(1, dots_start-1)) + local high = tonumber(word:sub(dots_end+1)) + -- perhaps someone passed a string as part of the range, intercept the issue + if not (low and high) then + wesnoth.message("Malformed range: " .. word) + table.insert(items, word) + num_choices = num_choices + 1 + else + if low > high then + -- low is greater than high, swap them + low, high = high, low + end + + -- if both ends represent the same number, then just use that number + if low == high then + table.insert(items, low) + num_choices = num_choices + 1 + else + -- insert a table representing the range + table.insert(items, {low, high}) + -- how many items does the range contain? Increase difference by 1 because we include both ends + num_choices = num_choices + (high - low) + 1 + end + end + else + -- handle as a string + table.insert(items, word) + num_choices = num_choices + 1 + end + end + else + num_choices = #possible_values + items = possible_values + -- We need to parse ranges separately anyway + for i, val in ipairs(possible_values) do + if type(val) == "table" then + assert(#val == 2 and type(val[1]) == "number" and type(val[2]) == "number", "Malformed range for helper.rand") + if val[1] > val[2] then + val = {val[2], val[1]} + end + num_choices = num_choices + (val[2] - val[1]) + end + end + end + + local idx = random_func(1, num_choices) + + for i, item in ipairs(items) do + if type(item) == "table" then -- that's a range + local elems = item[2] - item[1] + 1 -- amount of elements in the range, both ends included + if elems >= idx then + return item[1] + elems - idx + else + idx = idx - elems + end + else -- that's a single element + idx = idx - 1 + if idx == 0 then + return item + end + end + end + + return nil +end + +function mathx.shuffle(t, random_func) + random_func = random_func or mathx.random + -- since tables are passed by reference, this is an in-place shuffle + -- it uses the Fisher-Yates algorithm, also known as Knuth shuffle + assert(type(t) == "table", string.format("mathx.shuffle expects a table as parameter, got %s instead", type(t))) + local length = #t + for index = length, 2, -1 do + local random = random_func(1, index) + t[index], t[random] = t[random], t[index] + end +end + +wesnoth.random = wesnoth.deprecate_api('wesnoth.random', 'mathx.random', 1, nil, mathx.random) +wesnoth.get_time_stamp = wesnoth.deprecate_api('wesnoth.get_time_stamp', 'mathx.current_timestamp', 1, nil, mathx.current_timestamp) \ No newline at end of file diff --git a/data/lua/helper.lua b/data/lua/helper.lua index 4e7370c557ad..e0552f751927 100644 --- a/data/lua/helper.lua +++ b/data/lua/helper.lua @@ -86,114 +86,6 @@ function helper.adjacent_tiles(x, y, with_borders) end end -function helper.rand (possible_values, random_func) - random_func = random_func or wesnoth.random - assert(type(possible_values) == "table" or type(possible_values) == "string", - string.format("helper.rand expects a string or table as parameter, got %s instead", - type(possible_values))) - - local items = {} - local num_choices = 0 - - if type(possible_values) == "string" then - -- split on commas - for word in possible_values:gmatch("[^,]+") do - -- does the word contain two dots? If yes, that's a range - local dots_start, dots_end = word:find("%.%.") - if dots_start then - -- split on the dots if so and cast as numbers - local low = tonumber(word:sub(1, dots_start-1)) - local high = tonumber(word:sub(dots_end+1)) - -- perhaps someone passed a string as part of the range, intercept the issue - if not (low and high) then - wesnoth.message("Malformed range: " .. possible_values) - table.insert(items, word) - num_choices = num_choices + 1 - else - if low > high then - -- low is greater than high, swap them - low, high = high, low - end - - -- if both ends represent the same number, then just use that number - if low == high then - table.insert(items, low) - num_choices = num_choices + 1 - else - -- insert a table representing the range - table.insert(items, {low, high}) - -- how many items does the range contain? Increase difference by 1 because we include both ends - num_choices = num_choices + (high - low) + 1 - end - end - else - -- handle as a string - table.insert(items, word) - num_choices = num_choices + 1 - end - end - else - num_choices = #possible_values - items = possible_values - -- We need to parse ranges separately anyway - for i, val in ipairs(possible_values) do - if type(val) == "table" then - assert(#val == 2 and type(val[1]) == "number" and type(val[2]) == "number", "Malformed range for helper.rand") - if val[1] > val[2] then - val = {val[2], val[1]} - end - num_choices = num_choices + (val[2] - val[1]) - end - end - end - - local idx = random_func(1, num_choices) - - for i, item in ipairs(items) do - if type(item) == "table" then -- that's a range - local elems = item[2] - item[1] + 1 -- amount of elements in the range, both ends included - if elems >= idx then - return item[1] + elems - idx - else - idx = idx - elems - end - else -- that's a single element - idx = idx - 1 - if idx == 0 then - return item - end - end - end - - return nil -end - -function helper.round( number ) - -- code converted from util.hpp, round_portable function - -- round half away from zero method - if number >= 0 then - number = math.floor( number + 0.5 ) - else - number = math.ceil ( number - 0.5 ) - end - - return number -end - -function helper.shuffle( t, random_func ) - random_func = random_func or wesnoth.random - -- since tables are passed by reference, this is an in-place shuffle - -- it uses the Fisher-Yates algorithm, also known as Knuth shuffle - assert( - type( t ) == "table", - string.format( "helper.shuffle expects a table as parameter, got %s instead", type( t ) ) ) - local length = #t - for index = length, 2, -1 do - local random = random_func( 1, index ) - t[index], t[random] = t[random], t[index] - end -end - -- Compatibility and deprecations helper.distance_between = wesnoth.deprecate_api('helper.distance_between', 'wesnoth.map.distance_between', 1, nil, wesnoth.map.distance_between) helper.get_child = wesnoth.deprecate_api('helper.get_child', 'wml.get_child', 1, nil, wml.get_child) @@ -217,5 +109,8 @@ helper.shallow_parsed = wesnoth.deprecate_api('helper.shallow_parsed', 'wml.shal helper.set_wml_var_metatable = wesnoth.deprecate_api('helper.set_wml_var_metatable', 'wml.variable.proxy', 2, nil, helper.set_wml_var_metatable) helper.set_wml_tag_metatable = wesnoth.deprecate_api('helper.set_wml_tag_metatable', 'wml.tag', 2, nil, helper.set_wml_tag_metatable) helper.get_user_choice = wesnoth.deprecate_api('helper.get_user_choice', 'gui.get_user_choice', 1, nil, gui.get_user_choice) +helper.rand = wesnoth.deprecate_api('helper.rand', 'mathx.random_choice', 1, nil, mathx.random_choice) +helper.round = wesnoth.deprecate_api('helper.round', 'mathx.round', 1, nil, mathx.round) +helper.shuffle = wesnoth.deprecate_api('helper.shuffle', 'mathx.shuffle', 1, nil, mathx.shuffle) return helper diff --git a/projectfiles/VC16/wesnoth.vcxproj b/projectfiles/VC16/wesnoth.vcxproj index 2d80476f0aa4..4eea3e65cf59 100644 --- a/projectfiles/VC16/wesnoth.vcxproj +++ b/projectfiles/VC16/wesnoth.vcxproj @@ -2674,6 +2674,13 @@ $(IntDir)Scripting\ $(IntDir)Scripting\ + + $(IntDir)Scripting\ + $(IntDir)Scripting\ + $(IntDir)Scripting\ + $(IntDir)Scripting\ + $(IntDir)Scripting\ + $(IntDir)Scripting\ $(IntDir)Scripting\ @@ -4007,6 +4014,7 @@ + diff --git a/projectfiles/VC16/wesnoth.vcxproj.filters b/projectfiles/VC16/wesnoth.vcxproj.filters index d248b1c856b6..dc5df01f9e25 100644 --- a/projectfiles/VC16/wesnoth.vcxproj.filters +++ b/projectfiles/VC16/wesnoth.vcxproj.filters @@ -1557,6 +1557,9 @@ Scripting + + Scripting + Scripting @@ -3048,6 +3051,9 @@ Scripting + + Scripting + Scripting diff --git a/projectfiles/Xcode/The Battle for Wesnoth.xcodeproj/project.pbxproj b/projectfiles/Xcode/The Battle for Wesnoth.xcodeproj/project.pbxproj index 2ad7751fb3e7..391a290e9693 100644 --- a/projectfiles/Xcode/The Battle for Wesnoth.xcodeproj/project.pbxproj +++ b/projectfiles/Xcode/The Battle for Wesnoth.xcodeproj/project.pbxproj @@ -1034,6 +1034,7 @@ 91E3570A1CACC9B200774252 /* playcampaign.cpp in Sources */ = {isa = PBXBuildFile; fileRef = EC2F600B1A048E210018C9D6 /* playcampaign.cpp */; }; 91E3570B1CACC9B200774252 /* singleplayer.cpp in Sources */ = {isa = PBXBuildFile; fileRef = EC2F600C1A048E220018C9D6 /* singleplayer.cpp */; }; 91ECD5D21BA11A5200B25CF1 /* unit_creator.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 91ECD5D01BA11A5200B25CF1 /* unit_creator.cpp */; }; + 91F8E12E260A25E2002312BA /* lua_mathx.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 91F8E12D260A25E1002312BA /* lua_mathx.cpp */; }; 91FAC70A1C7FBC3400DAB2C3 /* lua_formula_bridge.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 91FAC7091C7FBC2C00DAB2C3 /* lua_formula_bridge.cpp */; }; 91FBBAD81CB6BC3F00470BFE /* filesystem_sdl.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 91FBBAD71CB6BC3F00470BFE /* filesystem_sdl.cpp */; }; 91FBBADB1CB6D1B700470BFE /* markov_generator.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 91FBBAD91CB6D1B700470BFE /* markov_generator.cpp */; }; @@ -2207,6 +2208,8 @@ 91ECD5D11BA11A5200B25CF1 /* unit_creator.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = unit_creator.hpp; sourceTree = ""; }; 91EF6BFC1C9E22E400E2A733 /* const_clone.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = const_clone.hpp; sourceTree = ""; }; 91EF6C001C9E22E400E2A733 /* reference_counter.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = reference_counter.hpp; sourceTree = ""; }; + 91F8E12C260A25E1002312BA /* lua_mathx.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = lua_mathx.hpp; sourceTree = ""; }; + 91F8E12D260A25E1002312BA /* lua_mathx.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = lua_mathx.cpp; sourceTree = ""; }; 91FAC7081C7F931900DAB2C3 /* lua_formula_bridge.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = lua_formula_bridge.hpp; sourceTree = ""; }; 91FAC7091C7FBC2C00DAB2C3 /* lua_formula_bridge.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = lua_formula_bridge.cpp; sourceTree = ""; }; 91FBBAD71CB6BC3F00470BFE /* filesystem_sdl.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = filesystem_sdl.cpp; sourceTree = ""; }; @@ -4630,9 +4633,12 @@ 91B621E91B76BB1500B00E0F /* lua_kernel_base.hpp */, ECA4A6791A1EC319006BCCF2 /* lua_map_location_ops.cpp */, 91B621EA1B76BB1800B00E0F /* lua_map_location_ops.hpp */, + 91F8E12D260A25E1002312BA /* lua_mathx.cpp */, + 91F8E12C260A25E1002312BA /* lua_mathx.hpp */, 9190B73A1CA0554900B0EF66 /* lua_pathfind_cost_calculator.hpp */, ECFB61831DA0A0C50055D3F8 /* lua_preferences.cpp */, ECFB61841DA0A0C50055D3F8 /* lua_preferences.hpp */, + 46E2D99525022D46003D99F3 /* lua_ptr.hpp */, ECC2FFF91A51A00900023AF4 /* lua_race.cpp */, 91B621EB1B76BB1D00B00E0F /* lua_race.hpp */, ECA4A67A1A1EC319006BCCF2 /* lua_rng.cpp */, @@ -4650,7 +4656,6 @@ 91B621EE1B76BB2C00B00E0F /* lua_unit_type.hpp */, 9193FC801D5C2CF7004F6C07 /* lua_unit.cpp */, 9193FC811D5C2CF8004F6C07 /* lua_unit.hpp */, - 46E2D99525022D46003D99F3 /* lua_ptr.hpp */, 46E2D98D25022BF5003D99F3 /* lua_widget_attributes.cpp */, 46E2D98A25022BF4003D99F3 /* lua_widget_attributes.hpp */, 46E2D98E25022BF5003D99F3 /* lua_widget_methods.cpp */, @@ -5290,6 +5295,7 @@ 46685C9D219D518B0009CFFE /* schema_validator.cpp in Sources */, ECF0F80123A09929004A2011 /* lua_stringx.cpp in Sources */, 46F92DE72174F6A400602C1C /* game_delete.cpp in Sources */, + 91F8E12E260A25E2002312BA /* lua_mathx.cpp in Sources */, 6295C3C4150FC9750077D8C5 /* map_fragment.cpp in Sources */, EC4E3B1D19B2D7AD0049CBD7 /* map_generator.cpp in Sources */, B5599B2C0EC62181008DD061 /* label.cpp in Sources */, diff --git a/source_lists/wesnoth b/source_lists/wesnoth index b3462ba41c6a..9565a0b22e0f 100644 --- a/source_lists/wesnoth +++ b/source_lists/wesnoth @@ -321,6 +321,7 @@ scripting/lua_formula_bridge.cpp scripting/lua_gui2.cpp scripting/lua_wml.cpp scripting/lua_stringx.cpp +scripting/lua_mathx.cpp scripting/lua_kernel_base.cpp scripting/lua_map_location_ops.cpp scripting/lua_preferences.cpp diff --git a/src/scripting/lua_kernel_base.cpp b/src/scripting/lua_kernel_base.cpp index 2af30827476a..7b0f1d9c68c5 100644 --- a/src/scripting/lua_kernel_base.cpp +++ b/src/scripting/lua_kernel_base.cpp @@ -19,7 +19,6 @@ #include "gui/core/gui_definition.hpp" // for remove_single_widget_definition #include "log.hpp" #include "lua_jailbreak_exception.hpp" // for lua_jailbreak_exception -#include "random.hpp" #include "seed_rng.hpp" #include "deprecation.hpp" #include "language.hpp" // for get_language @@ -37,6 +36,7 @@ #include "scripting/lua_wml.hpp" #include "scripting/lua_stringx.hpp" #include "scripting/lua_map_location_ops.hpp" +#include "scripting/lua_mathx.hpp" #include "scripting/lua_rng.hpp" #include "scripting/lua_widget.hpp" #include "scripting/push_check.hpp" @@ -281,36 +281,6 @@ static int intf_name_generator(lua_State *L) return 1; } -/** -* Returns a random numer, same interface as math.random. -*/ -static int intf_random(lua_State *L) -{ - if (lua_isnoneornil(L, 1)) { - double r = static_cast(randomness::generator->next_random()); - double r_max = static_cast(std::numeric_limits::max()); - lua_push(L, r / (r_max + 1)); - return 1; - } - else { - int32_t min; - int32_t max; - if (lua_isnumber(L, 2)) { - min = lua_check(L, 1); - max = lua_check(L, 2); - } - else { - min = 1; - max = lua_check(L, 1); - } - if (min > max) { - return luaL_argerror(L, 1, "min > max"); - } - lua_push(L, randomness::generator->get_random_int(min, max)); - return 1; - } -} - /** * Logs a message * Arg 1: (optional) Logger @@ -375,15 +345,6 @@ static int intf_get_image_size(lua_State *L) { return 2; } -/** -* Returns the time stamp, exactly as [set_variable] time=stamp does. -* - Ret 1: integer -*/ -static int intf_get_time_stamp(lua_State *L) { - lua_pushinteger(L, SDL_GetTicks()); - return 1; -} - static int intf_get_language(lua_State* L) { lua_push(L, get_language().localename); @@ -431,6 +392,7 @@ lua_kernel_base::lua_kernel_base() { "utf8", luaopen_utf8 }, // added in Lua 5.3 // Wesnoth libraries { "stringx",lua_stringx::luaW_open }, + { "mathx", lua_mathx::luaW_open }, { "wml", lua_wml::luaW_open }, { "gui", lua_gui2::luaW_open }, { nullptr, nullptr } @@ -483,10 +445,8 @@ lua_kernel_base::lua_kernel_base() { "compile_formula", &lua_formula_bridge::intf_compile_formula}, { "eval_formula", &lua_formula_bridge::intf_eval_formula}, { "name_generator", &intf_name_generator }, - { "random", &intf_random }, { "log", &intf_log }, { "get_image_size", &intf_get_image_size }, - { "get_time_stamp", &intf_get_time_stamp }, { "get_language", &intf_get_language }, { nullptr, nullptr } }; diff --git a/src/scripting/lua_mathx.cpp b/src/scripting/lua_mathx.cpp new file mode 100644 index 000000000000..e92610324c5b --- /dev/null +++ b/src/scripting/lua_mathx.cpp @@ -0,0 +1,91 @@ +/* + Copyright (C) 2014 - 2020 by the Battle for Wesnoth Project https://www.wesnoth.org/ + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY. + + See the COPYING file for more details. +*/ + +#include "scripting/lua_mathx.hpp" +#include "scripting/lua_kernel_base.hpp" +#include "scripting/lua_common.hpp" +#include "scripting/push_check.hpp" +#include "random.hpp" +#include "SDL2/SDL_timer.h" // for SDL_GetTicks + +#include "lua/lauxlib.h" +#include "lua/lua.h" +#include "lua/lualib.h" + +namespace lua_mathx { + +/** +* Returns a random number, same interface as math.random. +*/ +static int intf_random(lua_State* L) +{ + if (lua_isnoneornil(L, 1)) { + double r = static_cast(randomness::generator->next_random()); + double r_max = static_cast(std::numeric_limits::max()); + lua_push(L, r / (r_max + 1)); + return 1; + } + else { + int32_t min; + int32_t max; + if (lua_isnumber(L, 2)) { + min = lua_check(L, 1); + max = lua_check(L, 2); + } + else { + min = 1; + max = lua_check(L, 1); + } + if (min > max) { + return luaL_argerror(L, 1, "min > max"); + } + lua_push(L, randomness::generator->get_random_int(min, max)); + return 1; + } +} + +/** +* Returns the time stamp, exactly as [set_variable] time=stamp does. +* - Ret 1: integer +*/ +static int intf_get_time_stamp(lua_State* L) { + lua_pushinteger(L, SDL_GetTicks()); + return 1; +} + +static int intf_round(lua_State* L) { + double n = lua_tonumber(L, 1); + lua_pushinteger(L, std::round(n)); + return 1; +} + +int luaW_open(lua_State* L) { + auto& lk = lua_kernel_base::get_lua_kernel(L); + lk.add_log("Adding mathx module...\n"); + static luaL_Reg const math_callbacks[] = { + { "current_timestamp", &intf_get_time_stamp }, + { "random", &intf_random }, + { "round", &intf_round }, + { nullptr, nullptr }, + }; + lua_newtable(L); + luaL_setfuncs(L, math_callbacks, 0); + // Set the mathx metatable to index the math module + lua_createtable(L, 0, 1); + lua_getglobal(L, "math"); + lua_setfield(L, -2, "__index"); + lua_setmetatable(L, -2); + return 1; +} + +} diff --git a/src/scripting/lua_mathx.hpp b/src/scripting/lua_mathx.hpp new file mode 100644 index 000000000000..ac262cf7b3bc --- /dev/null +++ b/src/scripting/lua_mathx.hpp @@ -0,0 +1,20 @@ +/* + Copyright (C) 2014 - 2020 by the Battle for Wesnoth Project https://www.wesnoth.org/ + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY. + + See the COPYING file for more details. +*/ + +#pragma once + +struct lua_State; + +namespace lua_mathx { + int luaW_open(lua_State* L); +}