[solved] Storing a test condition as a string
Moderator: Forum Moderators
-
- Posts: 1253
- Joined: August 26th, 2018, 11:46 pm
- Location: A country place, far outside the Wire
[solved] Storing a test condition as a string
I have a table of preferences. For some preferences, I only want them to be visible if certain game events have occurred. So I'm looking to store my test condition as a string in the table for later execution. For example, the discovered field for the blood_rain item:
I'm not in love with that syntax, it's just what I have almost working, while under the assumption the "load" is the tool I want to use here.
Let's assume I want to determine whether or not to display the blood_rain preference. I can do this:
or
But what I can't figure out is how to do that in one line. Just execute the block and check the return value without the intermediate step of assigning said value to something first. I'm probably just being stubborn here, but I think I'm also displaying a fundamental lack of understanding of lua functions here and I'd like to fix that.
Code: Select all
table.insert(new_prefs,{ id = "show_map5", default = true , menu = "chapter5", discovered = true, type = "bool", value = true,
description = _"Show world map on entering scenario (chapter 5)"})
table.insert(new_prefs,{ id = "show_map9", default = true , menu = "chapter9", discovered = true, type = "bool", value = true,
description = _"Show world map on entering scenario (chapter 9)" })
table.insert(new_prefs,{ id = "blood_rain", default = true , menu = "chapter9", discovered = "return wml.variables.quests.seen_blood_rain == 1", type = "bool", value = true,
description = _"Show effects of blood rain" })
Let's assume I want to determine whether or not to display the blood_rain preference. I can do this:
Code: Select all
a=(load(new_prefs[3].discovered))
if(a()) then ...
Code: Select all
status,value = pcall(load(new_prefs[3].discovered))
if status and value then ...
Last edited by white_haired_uncle on March 13th, 2024, 3:27 am, edited 1 time in total.
Speak softly, and carry Doombringer.
- Celtic_Minstrel
- Developer
- Posts: 2258
- Joined: August 3rd, 2012, 11:26 pm
- Location: Canada
- Contact:
Re: Storing a test condition as a string
I'll answer your direct question in a moment, but before that I want to question why you're storing the condition as a string of Lua code. Is
Note: All these will assume you set the
new_prefs
a general Lua table, or does it need to be valid WML?Note: All these will assume you set the
$quests.seen_blood_rain
variable to yes instead of 1, which you probably should do anyway if it's meant to be a boolean value. That's not required for the examples to work, you just need to adjust them slightly if you have a good reason for it to be a number.- First, let me suppose it doesn't have to be WML. In that case, you're better off just putting a function in the table, like this:
And then when using it you can just call it:
Code: Select all
table.insert(new_prefs, { id = "blood_rain", default = true, menu = "chapter9", discovered = function() return wml.variables.quests.seen_blood_rain end, type = "bool", value = true })
Code: Select all
local discovered = new_prefs[3].discovered if type(discovered) == 'function' then discovered = discovered() end
- But, maybe it does have to be valid WML. If you need to serialize this data into the saved game value, it certainly needs to be WML-compatible, which means it can't contain functions. In that case, my personal recommendation would be to use WFL for the condition. The table would then look like this:
When using this version, you would need to evaluate the formula:
Code: Select all
table.insert(new_prefs, { id = "blood_rain", default = true, menu = "chapter9", -- Enclosing parenthesis not technically required, but I recommend them -- as an easy way to distinguish a formula from a yes/no discovered = "(quests.seen_blood_rain)" type = "bool", value = true })
Code: Select all
local discovered = new_prefs[3].discovered if type(discovered) == 'string' then discovered = wesnoth.eval_formula(discovered, wml.all_variables) end
- There is another option you could take if it's required to be WML – you could use ConditionalWML. That method would look like this:
When using this version, you would need to evaluate the conditional:
Code: Select all
local always_discovered = wml.tag.discovered{wml.tag['true']{}} table.insert(new_prefs,{ id = "show_map5", default = true , menu = "chapter5", always_discovered, type = "bool", value = true}) table.insert(new_prefs, { id = "blood_rain", default = true, menu = "chapter9", wml.tag.discovered{ wml.tag.variable{ name="quest.seen_blood_rain", boolean_equals=true }} type = "bool", value = true })
Code: Select all
local discovered = wml.get_child(new_prefs[3], "discovered") if discovered then discovered = wml.eval_conditional(discovered) end
- Lastly, the answer to your question, as best I understand. As far as I can, your first example is, in fact, correct, but it will fail for items 1 and 2 because
true
is not a valid Lua statement. To make the table structure you showed work, calling it like this should be fine:Though, that will raise an error if the string has a syntax error. If you'd prefer to ignore syntax errors and move on, something like this should work:Code: Select all
local discovered = new_prefs[3].discovered if type(discovered) == 'string' then discovered = load(discovered)() end
Code: Select all
local discovered = new_prefs[3].discovered if type(discovered) == 'string' then local f, error = load(discovered) if type(f) == 'function' then discovered = f() -- or, slightly safer: local status, result = pcall(f) if stats then discovered = result else -- at this point, the result variable contains what went wrong, in case you want to do something with it discovered = false end else -- at this point, the error variable contains what went wrong, in case you want to do something with it discovered = false end end
I can't think of any way to make a one-liner that will never throw an error. If you really want a one-liner though, the following should work:white_haired_uncle wrote: ↑March 13th, 2024, 1:23 am But what I can't figure out is how to do that in one line.However, for that code to work, all your cases ofCode: Select all
local discovered = load(new_prefs[3].discovered)()
discovered = true
would need to be changed todiscovered = "return true"
.
-
- Posts: 1253
- Joined: August 26th, 2018, 11:46 pm
- Location: A country place, far outside the Wire
Re: Storing a test condition as a string
Code: Select all
load(new_prefs[3].discovered)()
I stored the condition as lua code because that's what I could get to work. I'd prefer to just store the condition itself.
I assume this needs to be WML, since prefs.value at least will be accessed by WML and it will need to be saved between games (and perhaps even persist?).
quests.seen_blood_rain is 1, not yes, and that is more or less out of my control, unfortunately. Working around that should be the easiest part.
Solution #2 looks like the cleanest, though I'm unclear how it would handle errors. Solution #4 has my attention, mostly because the other day I was looking for a try/catch solution for lua and read about pcall and now I feel like I should be doing a lot more to trap errors as a habit (or I found a new toy and I'm looking for an excuse to play with it).
Thanks
Speak softly, and carry Doombringer.
- Celtic_Minstrel
- Developer
- Posts: 2258
- Joined: August 3rd, 2012, 11:26 pm
- Location: Canada
- Contact:
Re: Storing a test condition as a string
This means it will need to be WML, yes, so version #1 will not work for you.white_haired_uncle wrote: ↑March 13th, 2024, 3:26 am it will need to be saved between games (and perhaps even persist?).
Broadly speaking, it more or less doesn't, but it shouldn't crash in the event of an error. If there's an error in the formula string, you'll getwhite_haired_uncle wrote: ↑March 13th, 2024, 3:26 am Solution #2 looks like the cleanest, though I'm unclear how it would handle errors.
nil
back as the result, which would effectively mean that the item is never discovered.Indeed,white_haired_uncle wrote: ↑March 13th, 2024, 3:26 am Solution #4 has my attention, mostly because the other day I was looking for a try/catch solution for lua and read about pcall and now I feel like I should be doing a lot more to trap errors as a habit (or I found a new toy and I'm looking for an excuse to play with it).
pcall
is Lua's try-catch equivalent. I would say that the closest way to attain a try-catch idiom in Lua would be like this:Code: Select all
local status, error = pcall(function()
-- do some stuff here
end)
if not status then
-- do stuff with error here
end
Code: Select all
function f(a, b, c, d) return a + b, c .. d end
Code: Select all
local status, result1, result2 = pcall(f, a, b, 123, 'stuff')
if status then
-- do stuff with result1 and result2
else
local error = result1
-- do stuff with error
end
error
in Lua, since that's the name of a function (and functions as Lua's throw-equivalent, in fact). Oh well.Re: [solved] Storing a test condition as a string
I think there's a 5th solution that could be used here. You would need to create a global function:
Then store the name of this function (as a string) in the preferences table:
Finally, when you want to determine whether or not to display the blood_rain preference:
I haven't tested any of this, so it's possible this might not work for some reason, but it seems like this would be better than using
Code: Select all
-- This function needs to be global (we will see why later).
function blood_rain_discovered()
return wml.variables.quests.seen_blood_rain == 1
end
Code: Select all
table.insert(new_prefs,{ id = "blood_rain", default = true , menu = "chapter9", discovered = "blood_rain_discovered", type = "bool", value = true,
description = _"Show effects of blood rain" })
Code: Select all
-- We are looking for the function in the global variables table _G (this is why the function has to be global).
if _G[new_prefs[3].discovered]() then
...
load()
...- Celtic_Minstrel
- Developer
- Posts: 2258
- Joined: August 3rd, 2012, 11:26 pm
- Location: Canada
- Contact:
Re: [solved] Storing a test condition as a string
That's another good solution, yes. However, instead of a global function, I'd recommend namespacing it:
Store it the same as what gnombat posted, then use it like this:
Code: Select all
-- I'm starting the global variable with a capital letter only because the linting systems I use
-- consider lowercase names to be a probable error
PrefFunctions = {}
function PrefFunctions.blood_rain_discovered()
return wml.variables.quests.seen_blood_rain == 1
end
Code: Select all
if PrefFunctions[new_prefs[3].discovered]() then