Merge pull request #642 from Elvish-Hunter/set_variable_lua

Ported [set_variable] to Lua
This commit is contained in:
Celtic Minstrel 2016-06-06 12:47:15 -04:00
commit 3715c194c2
2 changed files with 163 additions and 238 deletions

View file

@ -1044,3 +1044,166 @@ function wml_actions.reset_fog(cfg)
wesnoth.add_fog(sides[i].side, locs, cfg.reset_view)
end
end
function wml_actions.set_variable(cfg)
local name = cfg.name or helper.wml_error "trying to set a variable with an empty name"
if cfg.value ~= nil then -- check for nil because user may try to set a variable as false
wesnoth.set_variable(name, cfg.value)
end
if cfg.literal ~= nil then
wesnoth.set_variable(name, helper.shallow_literal(cfg).literal)
end
if cfg.to_variable then
wesnoth.set_variable(name, wesnoth.get_variable(cfg.to_variable))
end
if cfg.add then
wesnoth.set_variable(name, (tonumber(wesnoth.get_variable(name)) or 0) + (tonumber(cfg.add) or 0))
end
if cfg.sub then
wesnoth.set_variable(name, (tonumber(wesnoth.get_variable(name)) or 0) - (tonumber(cfg.sub) or 0))
end
if cfg.multiply then
wesnoth.set_variable(name, (tonumber(wesnoth.get_variable(name)) or 0) * (tonumber(cfg.multiply) or 0))
end
if cfg.divide then
local divide = tonumber(cfg.divide) or 0
if divide == 0 then helper.wml_error("division by zero on variable " .. name) end
wesnoth.set_variable(name, (tonumber(wesnoth.get_variable(name)) or 0) / divide)
end
if cfg.modulo then
local modulo = tonumber(cfg.modulo) or 0
if modulo == 0 then helper.wml_error("division by zero on variable " .. name) end
wesnoth.set_variable(name, (tonumber(wesnoth.get_variable(name)) or 0) % modulo)
end
if cfg.round then
local var = tonumber(wesnoth.get_variable(name) or 0)
local round_val = cfg.round
if round_val == "ceil" then
wesnoth.set_variable(name, math.ceil(var))
elseif round_val == "floor" then
wesnoth.set_variable(name, math.floor(var))
else
local decimals, discarded = math.modf(tonumber(round_val) or 0)
local value = var * (10 ^ decimals)
value = helper.round(value)
value = value * (10 ^ -decimals)
wesnoth.set_variable(name, value)
end
end
-- unlike the other math operations, ipart and fpart do not act on
-- the value already contained in the variable
-- but on the value assigned to the respective key
if cfg.ipart then
local ivalue, fvalue = math.modf(tonumber(cfg.ipart) or 0)
wesnoth.set_variable(name, ivalue)
end
if cfg.fpart then
local ivalue, fvalue = math.modf(tonumber(cfg.fpart) or 0)
wesnoth.set_variable(name, fvalue)
end
if cfg.string_length ~= nil then
wesnoth.set_variable(name, string.len(tostring(cfg.string_length)))
end
if cfg.time then
if cfg.time == "stamp" then
wesnoth.set_variable(name, wesnoth.get_time_stamp())
end
end
if cfg.rand then
local random_string = cfg.rand
local num_choices = 0
local items = {}
-- split on commas
for word in random_string: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("<WML>","Malformed range: rand " .. cfg.rand)
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
local idx = wesnoth.random(1, num_choices)
local done = false
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
wesnoth.set_variable(name, item[1] + elems - idx)
done = true
break
else
idx = idx - elems
end
else -- that's a single element
idx = idx - 1
if idx == 0 then
wesnoth.set_variable(name, item)
done = true
end
end
if done then break end
end
end
local join_child = helper.get_child(cfg, "join")
if join_child then
local array_name = join_child.variable or helper.wml_error "missing variable= attribute in [join]"
local separator = join_child.separator
local key_name = join_child.key or "value"
local remove_empty = join_child.remove_empty
local string_to_join = {}
for i, element in ipairs(helper.get_variable_array(array_name)) do
if element[key_name] ~= nil or (not remove_empty) then
table.insert(string_to_join, tostring(element[key_name]))
end
end
wesnoth.set_variable(name, table.concat(string_to_join, separator))
end
end

View file

@ -607,244 +607,6 @@ WML_HANDLER_FUNCTION(set_global_variable,,pcfg)
verify_and_set_global_variable(pcfg);
}
WML_HANDLER_FUNCTION(set_variable,, cfg)
{
game_data *gameinfo = resources::gamedata;
const std::string name = cfg["name"];
const std::string to_variable = cfg["to_variable"];
try
{
if(name.empty()) {
ERR_NG << "trying to set a variable with an empty name:\n" << cfg.get_config().debug();
return;
}
config::attribute_value &var = gameinfo->get_variable(name);
config::attribute_value literal = cfg.get_config()["literal"]; // no $var substitution
if (!literal.blank()) {
var = literal;
}
config::attribute_value value = cfg["value"];
if (!value.blank()) {
var = value;
}
if(to_variable.empty() == false) {
var = gameinfo->get_variable_access_read(to_variable).as_scalar();
}
config::attribute_value add = cfg["add"];
if (!add.empty()) {
var = var.to_double() + add.to_double();
}
config::attribute_value sub = cfg["sub"];
if (!sub.empty()) {
var = var.to_double() - sub.to_double();
}
config::attribute_value multiply = cfg["multiply"];
if (!multiply.empty()) {
var = var.to_double() * multiply.to_double();
}
config::attribute_value divide = cfg["divide"];
if (!divide.empty()) {
if (divide.to_double() == 0) {
ERR_NG << "division by zero on variable " << name << std::endl;
return;
}
var = var.to_double() / divide.to_double();
}
config::attribute_value modulo = cfg["modulo"];
if (!modulo.empty()) {
if (modulo.to_double() == 0) {
ERR_NG << "division by zero on variable " << name << std::endl;
return;
}
var = std::fmod(var.to_double(), modulo.to_double());
}
config::attribute_value round_val = cfg["round"];
if (!round_val.empty()) {
double value = var.to_double();
if (round_val == "ceil") {
var = int(std::ceil(value));
} else if (round_val == "floor") {
var = int(std::floor(value));
} else {
// We assume the value is an integer.
// Any non-numerical values will be interpreted as 0
// Which is probably what was intended anyway
int decimals = round_val.to_int();
value *= std::pow(10.0, decimals); //add $decimals zeroes
value = round_portable(value); // round() isn't implemented everywhere
value *= std::pow(10.0, -decimals); //and remove them
var = value;
}
}
config::attribute_value ipart = cfg["ipart"];
if (!ipart.empty()) {
double result;
std::modf(ipart.to_double(), &result);
var = int(result);
}
config::attribute_value fpart = cfg["fpart"];
if (!fpart.empty()) {
double ignore;
var = std::modf(fpart.to_double(), &ignore);
}
config::attribute_value string_length_target = cfg["string_length"];
if (!string_length_target.blank()) {
var = int(string_length_target.str().size());
}
// Note: maybe we add more options later, eg. strftime formatting.
// For now make the stamp mandatory.
const std::string time = cfg["time"];
if(time == "stamp") {
var = int(SDL_GetTicks());
}
// Random generation works as follows:
// rand=[comma delimited list]
// Each element in the list will be considered a separate choice,
// unless it contains "..". In this case, it must be a numerical
// range (i.e. -1..-10, 0..100, -10..10, etc).
const std::string rand = cfg["rand"];
// The new random generator, the logic is a copy paste of the old random.
if(rand.empty() == false) {
assert(gameinfo);
// A default value in case something goes really wrong.
var = "";
std::string word;
std::vector<std::string> words;
std::vector<std::pair<long,long> > ranges;
long num_choices = 0;
std::string::size_type pos = 0, pos2 = std::string::npos;
std::stringstream ss(std::stringstream::in|std::stringstream::out);
while (pos2 != rand.length()) {
pos = pos2+1;
pos2 = rand.find(",", pos);
if (pos2 == std::string::npos)
pos2 = rand.length();
word = rand.substr(pos, pos2-pos);
words.push_back(word);
std::string::size_type tmp = word.find("..");
if (tmp == std::string::npos) {
// Treat this element as a string
ranges.push_back(std::pair<long, long>(0,0));
num_choices += 1;
}
else {
// Treat as a numerical range
const std::string first = word.substr(0, tmp);
const std::string second = word.substr(tmp+2,
rand.length());
long low, high;
ss << first + " " + second;
if ( !(ss >> low) || !(ss >> high) ) {
ERR_NG << "Malformed range: rand = \"" << rand << "\"" << std::endl;
// Treat this element as a string?
ranges.push_back(std::pair<long, long>(0,0));
num_choices += 1;
}
else {
if (low > high) {
std::swap(low, high);
}
ranges.push_back(std::pair<long, long>(low,high));
num_choices += (high - low) + 1;
// Make 0..0 ranges work
if (high == 0 && low == 0) {
words.pop_back();
words.push_back("0");
}
}
ss.clear();
}
}
assert(num_choices > 0);
// On most plattforms long can never hold a bigger value than a uint32_t, but there are exceptions where long is 64 bit.
if(static_cast<unsigned long>(num_choices) > std::numeric_limits<uint32_t>::max()) {
WRN_NG << "Requested random number with an upper bound of "
<< num_choices
<< " however the maximum number generated will be "
<< std::numeric_limits<uint32_t>::max()
<< ".\n";
}
uint32_t choice = random_new::generator->next_random() % num_choices;
uint32_t tmp = 0;
for(size_t i = 0; i < ranges.size(); ++i) {
tmp += (ranges[i].second - ranges[i].first) + 1;
if (tmp > choice) {
if (ranges[i].first == 0 && ranges[i].second == 0) {
var = words[i];
}
else {
var = (ranges[i].second - (tmp - choice)) + 1;
}
break;
}
}
}
const vconfig::child_list join_elements = cfg.get_children("join");
if(!join_elements.empty())
{
const vconfig & join_element = join_elements.front();
std::string array_name=join_element["variable"];
std::string separator=join_element["separator"];
std::string key_name=join_element["key"];
if(key_name.empty())
{
key_name="value";
}
bool remove_empty = join_element["remove_empty"].to_bool();
std::string joined_string;
variable_access_const vi = resources::gamedata->get_variable_access_read(array_name);
bool first = true;
for (const config &cfg : vi.as_array())
{
std::string current_string = cfg[key_name];
if (remove_empty && current_string.empty()) continue;
if (first) first = false;
else joined_string += separator;
joined_string += current_string;
}
var = joined_string;
}
}
catch(const invalid_variablename_exception&)
{
ERR_NG << "Found invalid variablename in \n[set_variable]\n" << cfg.get_config().debug() << "[/set_variable]\n where name = " << name << " to_variable = " << to_variable << "\n";
}
}
WML_HANDLER_FUNCTION(set_variables,, cfg)
{
const t_string& name = cfg["name"];