Merge pull request #642 from Elvish-Hunter/set_variable_lua
Ported [set_variable] to Lua
This commit is contained in:
commit
3715c194c2
2 changed files with 163 additions and 238 deletions
|
@ -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
|
||||
|
|
|
@ -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"];
|
||||
|
|
Loading…
Add table
Reference in a new issue