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);
+}