Another basic lua query

Discussion of Lua and LuaWML support, development, and ideas.

Moderator: Forum Moderators

User avatar
Spannerbag
Posts: 777
Joined: December 18th, 2016, 6:14 pm
Location: Yes

Re: Another basic lua query: location set

Post by Spannerbag »

Celtic_Minstrel wrote: June 10th, 2025, 12:34 pm ...you can shorten it a bit t local rand_x,rand_y = reach_locs:random().
...It's not all that complicated. It maps from locations to values, by converting the location to a numeric key in a Lua table.
Ah, light shineth... people often credit me with more/less knowledge/stupidity than I possess... :augh:
Thanks for dumbing down stuff sufficiently for me to understand. :thumbsup:
As ever your patience is much appreciated.

gnombat wrote: June 10th, 2025, 11:31 am There is an example in data/ai/micro_ais/cas/ca_herding_sheep_move.lua
Thanks gnombat for the example, as it happens I only logged in to post a minor bug report on 1.19 so read Celtic_Minstrel's post before yours - but that was helpful because I had no idea an alternate syntax existed!

Explains why my text search didn't find anything... :doh:

Thanks!
-- Spannerbag
SP Campaigns: After EI (v1.14) Leafsea Burning (v1.18, v1.16)
I suspect the universe is simpler than we think and stranger than we can know.
Also, I fear that beyond a certain point more intelligence does not necessarily benefit a species...
User avatar
Spannerbag
Posts: 777
Joined: December 18th, 2016, 6:14 pm
Location: Yes

Re: Another basic lua query: hopefully brief syntax check

Post by Spannerbag »

Hi,
Found time to do a bit more on this recently and am making progress, albeit slooowly... :x

I'm not sure of some syntax and am just checking to see if I've got it right?

I'm stealing the "protective parents" ca logic from forest animals (ca_forest_animals_tusker_attack.lua).
Specifically it's this snippet I'm unsure of how to rewrite:

Code: Select all

    -- The tusker moves as close to enemy as possible
    -- Closeness to tusklets is secondary criterion
    local adj_tusklets = wesnoth.units.find_on_map {
        side = wesnoth.current.side,
        type = cfg.tusklet_type,
        { "filter_adjacent", { id = target.id } }
    }
Instead of cfg.tusklet_type, the method used to identify the tusklet equivalent ("follower") in my version is a unit filter:

Code: Select all

      [filter_follower]
        type=Icemonax
        [filter_wml]
          [variables]
            wmai=1
          [/variables]
        [/filter_wml]
      [/filter_follower]
I also want to honour [avoid] whilst moving the protecting unit ("wanderer").

So I have this code for the protect action (it's not finished, I just want to minimise syntax errors here :) ).

Code: Select all

-- ca_wanderers_protect.lua

local AH = wesnoth.require "ai/lua/ai_helper.lua"
local M = wesnoth.map


local function get_wanderers(cfg)
  local wanderers = AH.get_units_with_moves { side = wesnoth.current.side, { "and", filter_wanderer } }
  return wanderers
end


-- Returns enemies adjacent to follower units
-- function ai_helper.get_attackable_enemies(filter, side, cfg)

-- ****************************************************
-- *** REFERENCED BY QUESTION 1 BELOW ***
-- ****************************************************

  local function get_adjacent_enemies(cfg)
  local adjacent_enemies = AH.get_attackable_enemies( { "filter_adjacent", { side = wesnoth.current.side, { "and", filter_follower } } }
						      side = wesnoth.current.side
						      avoid_map = avoid_locs )
  return adjacent_enemies
end


local ca_wanderers_protect = {}

-- Check whether there is an enemy next to a follower and attack it (if disable_protect=false)
function ca_wanderers_protect:evaluation(cfg)
  if cfg.disable_protect		then return 0 end  -- Protect followers disabled (not default)
  if (not filter_wanderer)		then return 0 end  -- No filter_wanderer tag
  if (not filter_follower)		then return 0 end  -- No filter_follower tag
  if (not get_wanderers(cfg)[1])	then return 0 end  -- No wanderers with moves, so nothing to move
  if (not get_adjacent_enemies(cfg)[1])	then return 0 end  -- No adjacent enemies
  return cfg.ca_score
end

function ca_wanderers_protect:execution(cfg)
  local wanderers	 = get_wanderers(cfg)
  local adjacent_enemies = get_adjacent_enemies(cfg)
  local avoid_locs	 = AH.get_avoid_map(ai, nil, true)  -- [avoid]ed locations, if any

  -- Find the closest enemy to any wanderer
  ---@type number, unit?,  unit?
  local min_dist, attacker, target = math.huge, nil, nil
  for _,wanderer in ipairs(wanderers) do
    for _,enemy in ipairs(adjacent_enemies) do
      local dist = M.distance_between(wanderer.x, wanderer.y, enemy.x, enemy.y)
      if (dist < min_dist) then min_dist, attacker, target = dist, wanderer, enemy end
    end
  end


-- ****************************************************
-- *** REFERENCED BY QUESTION 2 BELOW ***
-- ****************************************************

  -- The wanderer moves as close to enemy as possible
  -- Closeness to followers is secondary criterion
  -- wesnoth.units.find_on_map(filter) → array of units
  local adj_followers = wesnoth.units.find_on_map { side = wesnoth.current.side, { "and", filter_follower } { "and", "filter_adjacent", { id = target.id } } }
  local best_hex = AH.find_best_move(attacker, function(x, y), { avoid_map = avoid_locs }
  local rating = -M.distance_between(x, y, target.x, target.y)
  for _,follower in ipairs(adj_followers) do
    if (M.distance_between(x, y, follower.x, follower.y) == 1) then rating = rating + 0.1 end
  end
  return rating
  end)

  AH.robust_move_and_attack(ai, attacker, best_hex, target)
end

return ca_wanderers_protect


Questions:
  1. Follower filter is stored thus: local filter_follower = wml.get_child(cfg,"filter_follower")

    Code: Select all

      local adjacent_enemies = AH.get_attackable_enemies( { "filter_adjacent", { side = wesnoth.current.side, { "and", filter_follower } } }
    						      side = wesnoth.current.side
    						      avoid_map = avoid_locs )
     
    I'd like this to honour [avoid] when moving attacking wanderers (more for consistency than anything else, if it's complicated I can omit it :) ).
    Is the best place to add that here (presumably removing some otherwise attackable enemies due to [avoid]) or process avoided locations later?

    Presently I specify side= so the function knows that avoid_map is indeed that and not a garbled side - is there a more elegant way to do this?

    Is enclosing parameters with "(...)" correct or should I use "{...}"?

    Finally: any other syntax errors?
  2. When using the existing logic I need to identify enemies adjacent to followers.
    Original reference code uses type = cfg.tusklet_type whereas I have filter_follower.

    Code: Select all

      local adj_followers = wesnoth.units.find_on_map { side = wesnoth.current.side, { "and", filter_follower } { "and", "filter_adjacent", { id = target.id } } }
      
    Can I simply append filter_adjacent and if so have I done it correctly?
    Alternatively, is there a better way given what I have to work with?
    E.g. maybe working with the enemy units adjacent to followers rather than vice-versa?

I've kept my questions to a minimun (I have loads more but those I'll work on) but these would be quick wins if anyone could point out the more glaring errors! :)

Thanks in advance for your time and trouble, much appreciated as always.

Cheers!
-- Spannerbag
SP Campaigns: After EI (v1.14) Leafsea Burning (v1.18, v1.16)
I suspect the universe is simpler than we think and stranger than we can know.
Also, I fear that beyond a certain point more intelligence does not necessarily benefit a species...
User avatar
Celtic_Minstrel
Developer
Posts: 2376
Joined: August 3rd, 2012, 11:26 pm
Location: Canada
Contact:

Re: Another basic lua query: hopefully brief syntax check

Post by Celtic_Minstrel »

Spannerbag wrote: June 20th, 2025, 12:00 pm Follower filter is stored thus: local filter_follower = wml.get_child(cfg,"filter_follower")

Code: Select all

  local adjacent_enemies = AH.get_attackable_enemies( { "filter_adjacent", { side = wesnoth.current.side, { "and", filter_follower } } }
						      side = wesnoth.current.side
						      avoid_map = avoid_locs )
 
That code is absolutely not valid. I'm not sure what will happen, but you'll probably get a syntax error.
  1. You need to separate multiple parameters with commas.
  2. There's no such thing as using equals to specify the name of a parameter.
The specification of the function you're using is this:

Code: Select all

---Attackable enemies are defined as being being
---  - enemies of the specified side,
---  - not petrified
---  - and visible to the side as defined in cfg.viewing_side and cfg.ignore_visibility.
---  - have at least one adjacent hex that is not inside an area to avoid
---For speed reasons, this is done separately, rather than calling ai_helper.get_visible_units().
---@param filter? WMLTable Standard unit filter WML table for the enemies
---@param side? integer Side number, if side other than current side is to be considered
---@param cfg? get_enemies_opts
---@return table
In other words, it takes 3 arguments: a filter, a side number, and an option table.
Spannerbag wrote: June 20th, 2025, 12:00 pm Presently I specify side= so the function knows that avoid_map is indeed that and not a garbled side - is there a more elegant way to do this?
That does absolutely nothing. It's not even valid syntax. If it were valid (because you're in "{...}" and not "(...)" then it would be required and fine, but that's not the case here.
Spannerbag wrote: June 20th, 2025, 12:00 pm Is enclosing parameters with "(...)" correct or should I use "{...}"?
Parameters are always enclosed in "(...)", not "{...}". If there is a single parameter which is enclosed in "{...}" then you can omit the "(...)", so it looks like your parameters are enclosed in "{...}", but the truth is you just have one parameter. But that doesn't apply here – you need 3 parameters. (You can also omit the parentheses if the single parameter is a string, which means it's enclosed in "double quotes", 'single quotes', or [[double brackets]].)

Now, to get back to the question… as stated previously, the function you're calling takes 3 parameters:
  • A filter. You've got that one right: { "filter_adjacent", { side = wesnoth.current.side, { "and", filter_follower } } }
  • A side number: wesnoth.current.side
    ]*]An options table. From what you've said, you want to put the avoid map here: {avoid_map = avoid_locs}
Puting it all together, and making sure there is a comma between all the arguments, and you get this:

Code: Select all

  local adjacent_enemies = AH.get_attackable_enemies( { "filter_adjacent", { side = wesnoth.current.side, { "and", filter_follower } } },
						      wesnoth.current.side,
						      {avoid_map = avoid_locs} )
 
Spannerbag wrote: June 20th, 2025, 12:00 pm When using the existing logic I need to identify enemies adjacent to followers.
Original reference code uses type = cfg.tusklet_type whereas I have filter_follower.

Code: Select all

  local adj_followers = wesnoth.units.find_on_map { side = wesnoth.current.side, { "and", filter_follower } { "and", "filter_adjacent", { id = target.id } } }
  
Can I simply append filter_adjacent and if so have I done it correctly?
Alternatively, is there a better way given what I have to work with?
E.g. maybe working with the enemy units adjacent to followers rather than vice-versa?
It looks like you have it right in terms of syntax, except for one error: you're missing a comma in there. Though I'm not sure if that filter is composed correctly.
Author of The Black Cross of Aleron campaign and Default++ era.
Former maintainer of Steelhive.
User avatar
Spannerbag
Posts: 777
Joined: December 18th, 2016, 6:14 pm
Location: Yes

Re: Another basic lua query

Post by Spannerbag »

Thanks ever so much for your reply, really really helpful.
Celtic_Minstrel wrote: June 20th, 2025, 12:40 pm ...It looks like you have it right in terms of syntax, except for one error: you're missing a comma in there. Though I'm not sure if that filter is composed correctly.
Assuming I correctly deciphered the original code logic:

Code: Select all

    -- The tusker moves as close to enemy as possible
    -- Closeness to tusklets is secondary criterion
    local adj_tusklets = wesnoth.units.find_on_map {
        side = wesnoth.current.side,
        type = cfg.tusklet_type,
        { "filter_adjacent", { id = target.id } }
    }
I think :fingers_crossed: this results in this filter (plugging in actual values):

Code: Select all

  side = 3
  type = Icemonax
  [filter_adjacent]
    id=target.id
  [/filter_adjacent]
The structure I'm trying to build is (assuming I'm right so far) is as above but with type replaced by a full filter:

Code: Select all

  side = 3
  type = Icemonax
  [filter_wml]
    [variables]
      wmai=1
    [/variables]
  [/filter_wml]
  [filter_adjacent]
    id=target.id
  [/filter_adjacent]
My attempt (with added comma :) ):

Code: Select all

  local adj_followers = wesnoth.units.find_on_map {
                                    side = wesnoth.current.side,
                                    { "and", filter_follower }, { "and", "filter_adjacent", { id = target.id } }
                                    }


or, embed filter_adjacent?:

Code: Select all

  local adj_followers = wesnoth.units.find_on_map {
                                    side = wesnoth.current.side,
                                    { "and", filter_follower, { "and", "filter_adjacent", { id = target.id }} }
                                    }


Hope this makes sense.

Thanks again for your patience!

Cheers!
-- Spannerbag
SP Campaigns: After EI (v1.14) Leafsea Burning (v1.18, v1.16)
I suspect the universe is simpler than we think and stranger than we can know.
Also, I fear that beyond a certain point more intelligence does not necessarily benefit a species...
gnombat
Posts: 898
Joined: June 10th, 2010, 8:49 pm

Re: Another basic lua query

Post by gnombat »

Spannerbag wrote: June 20th, 2025, 12:00 pm Finally: any other syntax errors?
The find_best_move line is not right. It looks like you were trying to add a third argument?

There are some examples of how to call find_best_move with a third argument in existing code, e.g., ca_swarm_move.lua
Spannerbag wrote: June 20th, 2025, 3:17 pm The structure I'm trying to build is (assuming I'm right so far) is as above but with type replaced by a full filter:

Code: Select all

  side = 3
  type = Icemonax
  [filter_wml]
    [variables]
      wmai=1
    [/variables]
  [/filter_wml]
  [filter_adjacent]
    id=target.id
  [/filter_adjacent]
My attempt (with added comma :) ):

Code: Select all

  local adj_followers = wesnoth.units.find_on_map {
                                    side = wesnoth.current.side,
                                    { "and", filter_follower }, { "and", "filter_adjacent", { id = target.id } }
                                    }


or, embed filter_adjacent?:

Code: Select all

  local adj_followers = wesnoth.units.find_on_map {
                                    side = wesnoth.current.side,
                                    { "and", filter_follower, { "and", "filter_adjacent", { id = target.id }} }
                                    }
I don't think either of those looks right... Before trying to fix those, a couple of questions:
  1. Have you considered using wml.tag for representing WML? This would make the code simpler (it reduces the number of braces you need by 50%).
  2. Do you need to use and there? Usually you don't need and (and there's no [and] in the WML you posted). That would simplify the code further.
User avatar
Spannerbag
Posts: 777
Joined: December 18th, 2016, 6:14 pm
Location: Yes

Re: Another basic lua query

Post by Spannerbag »

First off, thanks for replying, your time and trouble are appreciated as always! :D
gnombat wrote: June 21st, 2025, 7:20 am [The find_best_move line is not right. It looks like you were trying to add a third argument?
I just looked at the function (in data/ai/lua/ai_helper.lua):

Code: Select all

function ai_helper.find_best_move(units, rating_function, cfg)
    cfg = cfg or {}
    ...
... within which cfg is referenced: local reach_map = ai_helper.get_reachable_unocc(unit, cfg)

... which in turn has: ai_helper.get_reachmap(unit, cfg_GRU)

... within which (finally) we drill down to:

Code: Select all

function ai_helper.get_reachmap(unit, cfg)
    --   plus all other parameters to wesnoth.paths.find_reach

    local viewing_side = cfg and cfg.viewing_side or unit.side
    ai_helper.check_viewing_side(viewing_side)
    local ignore_visibility = cfg and cfg.ignore_visibility

    local old_moves = unit.moves
    if cfg and (cfg.moves == 'max') then unit.moves = unit.max_moves end

    local reachmap = LS.create()
    local initial_reach = wesnoth.paths.find_reach(unit, cfg)
    for _,loc in ipairs(initial_reach) do
        local is_available = true
        if cfg and cfg.avoid_map and cfg.avoid_map:get(loc[1], loc[2]) then
            is_available = false
            ...
So my hope/assumption is that by adding the avoid map to the original call it will pass through to function ai_helper.get_reachmap thereby taking any avoided locations into consideration.
This is why I'm asking so many questions; I run out of stack space storing and combining so many nested queries in my head! :lol:

gnombat wrote: June 21st, 2025, 7:20 am There are some examples of how to call find_best_move with a third argument in existing code, e.g., ca_swarm_move.lua...

Thanks for the pointer but it doesn't really help me with the basics; the 3rd argument is composite (I guess) as many lines of code occupy where I expected to see a 3rd argument?
This extra detail rather gets in the way of seeing how things fit together - at least for me whilst I'm still getting used to lua.
Alas, don't have much quality thinking time just now so apologies if I ask dumb questions!

gnombat wrote: June 21st, 2025, 7:20 am ...I don't think either of those looks right... Before trying to fix those, a couple of questions:
  1. Have you considered using wml.tag for representing WML? This would make the code simpler (it reduces the number of braces you need by 50%).
  2. Do you need to use and there? Usually you don't need and (and there's no [and] in the WML you posted). That would simplify the code further.
1. No, didn't think of that.
Would be willing to give it a go - but I might miss errors due to logic being presented differently! :doh:

2. No idea, copied from other code not a clue whether and "glue" is mandatory when building filters and/or sub-filters within lua.
However wml.tag might really help here! :fingers_crossed:

I think I might have to revert to a much earlier and simpler (but essentially working) version that was (even more) just a simple tweak of forest animals.
Then gradually add/change things, I know more now than I did then so maybe starting over from scratch (even if it entails major surgery) might be quicker? :thinking:

Thanks again for your help, really appreciated!

Cheers!
-- Spannerbag
SP Campaigns: After EI (v1.14) Leafsea Burning (v1.18, v1.16)
I suspect the universe is simpler than we think and stranger than we can know.
Also, I fear that beyond a certain point more intelligence does not necessarily benefit a species...
gnombat
Posts: 898
Joined: June 10th, 2010, 8:49 pm

Re: Another basic lua query

Post by gnombat »

Spannerbag wrote: June 21st, 2025, 11:06 am
gnombat wrote: June 21st, 2025, 7:20 am There are some examples of how to call find_best_move with a third argument in existing code, e.g., ca_swarm_move.lua...
Thanks for the pointer but it doesn't really help me with the basics; the 3rd argument is composite (I guess) as many lines of code occupy where I expected to see a 3rd argument?
No, it's actually the 2nd argument that spans many lines of code. The 2nd argument is a function that starts on line 45 and ends on line 74 (the end keyword is the end of that function). The 3rd argument { avoid_map = avoid_map } is on line 74 after that.

It's going to look weird if you're not used to programming languages like Lua with anonymous functions, but you'll get used to it.
Spannerbag wrote: June 21st, 2025, 11:06 am 2. No idea, copied from other code not a clue whether and "glue" is mandatory when building filters and/or sub-filters within lua.
However wml.tag might really help here! :fingers_crossed:
and here is just representing the WML [and] tag. Usually you don't need the [and] tag in WML (except maybe for very complex logic?), so I think you usually won't need it when representing WML in Lua.

This is what you posted for WML:

Code: Select all

  side = 3
  type = Icemonax
  [filter_wml]
    [variables]
      wmai=1
    [/variables]
  [/filter_wml]
  [filter_adjacent]
    id=target.id
  [/filter_adjacent]
If you want to represent that literally in Lua, I think it would look like this:

Code: Select all

{
  side = 3,
  type = 'Icemonax',
  wml.tag.filter_wml {
    wml.tag.variables { wmai = 1 }
  },
  wml.tag.filter_adjacent { id = target.id }
}
User avatar
Celtic_Minstrel
Developer
Posts: 2376
Joined: August 3rd, 2012, 11:26 pm
Location: Canada
Contact:

Re: Another basic lua query

Post by Celtic_Minstrel »

gnombat wrote: June 21st, 2025, 7:18 pm and here is just representing the WML [and] tag. Usually you don't need the [and] tag in WML (except maybe for very complex logic?), so I think you usually won't need it when representing WML in Lua.
I think it's used quote often in the AI code, actually – likely to head off errors that might otherwise occur if someone passed in a filter containing [or] tags.
Author of The Black Cross of Aleron campaign and Default++ era.
Former maintainer of Steelhive.
User avatar
Spannerbag
Posts: 777
Joined: December 18th, 2016, 6:14 pm
Location: Yes

Re: Another basic lua query

Post by Spannerbag »

gnombat wrote: June 21st, 2025, 7:18 pm No, it's actually the 2nd argument that spans many lines of code. The 2nd argument is a function that starts on line 45 and ends on line 74 (the end keyword is the end of that function). The 3rd argument { avoid_map = avoid_map } is on line 74 after that.

It's going to look weird if you're not used to programming languages like Lua with anonymous functions, but you'll get used to it.
Ah-ha! That explains a lot and hugely clarifies a few (of several) things that puzzled me! Many thanks! :D
I did try reading the lua manual but it's rather abstract; I find it a much swifter process to work with actual code (even if it can be frustrating at times) as I have a knack of misunderstanding things manuals rarely seem to elaborate on... hmm, think I have my answer: I can find ambiguity in anything. :augh:

gnombat wrote: June 21st, 2025, 7:18 pm This is what you posted for WML:

Code: Select all

  side = 3
  type = Icemonax
  [filter_wml]
    [variables]
      wmai=1
    [/variables]
  [/filter_wml]
  [filter_adjacent]
    id=target.id
  [/filter_adjacent]
If you want to represent that literally in Lua, I think it would look like this:

Code: Select all

{
  side = 3,
  type = 'Icemonax',
  wml.tag.filter_wml {
    wml.tag.variables { wmai = 1 }
  },
  wml.tag.filter_adjacent { id = target.id }
}
That ... actually makes sense! :shock:
  • type is a string assignment hence quotes, others are numbers.
  • Use {...] construct because, to quote the wiki: Many functions in the Wesnoth Lua API expect a table representing a WML object...
  • Put comma after every parameter (except the last one of course) to tell lua where each one ends.
I think you've actually pounded something useful into my head. ^_^

Many thanks for your assistance, as always.

Cheers!
-- Spannerbag
SP Campaigns: After EI (v1.14) Leafsea Burning (v1.18, v1.16)
I suspect the universe is simpler than we think and stranger than we can know.
Also, I fear that beyond a certain point more intelligence does not necessarily benefit a species...
User avatar
Spannerbag
Posts: 777
Joined: December 18th, 2016, 6:14 pm
Location: Yes

Re: Another basic lua query

Post by Spannerbag »

Celtic_Minstrel wrote: June 22nd, 2025, 1:30 am
gnombat wrote: June 21st, 2025, 7:18 pm and here is just representing the WML [and] tag. Usually you don't need the [and] tag in WML (except maybe for very complex logic?), so I think you usually won't need it when representing WML in Lua.
I think it's used quote often in the AI code, actually – likely to head off errors that might otherwise occur if someone passed in a filter containing [or] tags.
Thanks for making that point, in a WML context I guess I can drop the "and" as (I presume) anything inside a wml.tag uses the same [and] rule as WML.

So; to filter all leaders and all side 2 units:

Code: Select all

[filter]
  canrecruit=yes
  [or]
    side=2
  [/or]
[filter]
in lua would be, I presume:

Code: Select all

{
  canrecruit='yes',
  {'or', side=2},
}

Whereas to filter leaders of side 2 and/or who are undead would be:

Code: Select all

[filter]
  canrecruit=yes
  [and]
     side=2
    [or]
    race=undead
    [/or]
  [/and]
[filter]
I.e.

Code: Select all

{
  canrecruit='yes',
  {'and',
    { side=2 },
    { 'or', race='undead' }
  }
}
Alternatively:

Code: Select all

{
  canrecruit='yes',
  wml.tag.and { side=2, wml.tag.or {race='undead'} }
}
Have I got the logic and syntax correct?

Your continuing help and support is really useful and very enlightening!

Just the small task of recode and test left to do (shudder). :lol:

Cheers!
-- Spannerbag
SP Campaigns: After EI (v1.14) Leafsea Burning (v1.18, v1.16)
I suspect the universe is simpler than we think and stranger than we can know.
Also, I fear that beyond a certain point more intelligence does not necessarily benefit a species...
User avatar
Ravana
Forum Moderator
Posts: 3322
Joined: January 29th, 2012, 12:49 am
Location: Estonia
Contact:

Re: Another basic lua query

Post by Ravana »

Some of your examples are not valid Lua syntax. For example missing {} in wml table creation and using Lua keyword inappropriately. wml.tag["and"]
User avatar
Celtic_Minstrel
Developer
Posts: 2376
Joined: August 3rd, 2012, 11:26 pm
Location: Canada
Contact:

Re: Another basic lua query

Post by Celtic_Minstrel »

Spannerbag wrote: June 22nd, 2025, 11:31 am

Code: Select all

{
  canrecruit='yes',
  wml.tag.and { side=2, wml.tag.or {race='undead'} }
}
Sadly, though this is correct in spirit, it won't work in practice. That's because "and" and "or" are words that Lua considers special, so you can't use them there. You would need to either use the representation without wml.tag here, or the alternate way of calling wml.tag that Ravana mentioned:

Code: Select all

{
  canrecruit='yes',
  wml.tag['and'] { side=2, wml.tag['or'] {race='undead'} }
}
Spannerbag wrote: June 22nd, 2025, 11:31 am

Code: Select all

{
  canrecruit='yes',
  {'and',
    { side=2 },
    { 'or', race='undead' }
  }
}
This one is also wrong. There shouldn't be an extra set of braces aroung side=2, but you do need braces around the full content of 'and':

Code: Select all

{
  canrecruit='yes',
  {'and', {
    side=2,
    { 'or', race='undead' }
  }}
}
Author of The Black Cross of Aleron campaign and Default++ era.
Former maintainer of Steelhive.
gnombat
Posts: 898
Joined: June 10th, 2010, 8:49 pm

Re: Another basic lua query

Post by gnombat »

Spannerbag wrote: June 22nd, 2025, 10:56 am Put comma after every parameter (except the last one of course) to tell lua where each one ends.
Actually, you can put a comma after the last one if you want; Lua will allow a trailing comma in a table constructor.

(This only works for tables - Lua doesn't allow a trailing comma in other places like function calls.)
User avatar
Spannerbag
Posts: 777
Joined: December 18th, 2016, 6:14 pm
Location: Yes

Re: Another basic lua query

Post by Spannerbag »

Ravana wrote: June 22nd, 2025, 11:57 am Some of your examples are not valid Lua syntax. For example missing {} in wml table creation and using Lua keyword inappropriately. wml.tag["and"]
Celtic_Minstrel wrote: June 22nd, 2025, 1:20 pm
Spannerbag wrote: June 22nd, 2025, 11:31 am

Code: Select all

{
  canrecruit='yes',
  wml.tag.and { side=2, wml.tag.or {race='undead'} }
}
Sadly, though this is correct in spirit, it won't work in practice. That's because "and" and "or" are words that Lua considers special, so you can't use them there...
OK, can't use lua reserved words in wml.tag, got it (hope I remember it!). :)
Thanks for pointing that out.

Celtic_Minstrel wrote: June 22nd, 2025, 1:20 pm...You would need to either use the representation without wml.tag here, or the alternate way of calling wml.tag that Ravana mentioned:

Code: Select all

{
  canrecruit='yes',
  wml.tag['and'] { side=2, wml.tag['or'] {race='undead'} }
}
Spannerbag wrote: June 22nd, 2025, 11:31 am

Code: Select all

{
  canrecruit='yes',
  {'and',
    { side=2 },
    { 'or', race='undead' }
  }
}
This one is also wrong. There shouldn't be an extra set of braces aroung side=2, but you do need braces around the full content of 'and':

Code: Select all

{
  canrecruit='yes',
  {'and', {
    side=2,
    { 'or', race='undead' }
  }}
}
Aaarghh, forgot I was using and in lua context (not wml.tag). :doh:

Right... I think I've got that straight...

I was mulling over maybe having a hack at some text and examples for a wiki page: Lua introduction for WML coders for those who are interested - once I feel a bit more confident myself of course (anything I write now would be a great example of how not to do stuff in lua...).
Incorporating lua into a campaign is well documented and the existing wiki is a great reference once you know what you're doing.
However some grounding in basic syntax (with relevant examples) might provide people new to lua some signposts around the more common gotchas?
That is, it's not meant to be an exhaustive tutorial more a launch pad so readers aren't (quite as) overloaded when they refer to resources that assume knowledge/familiarity they may not have (and would maybe struggle to acquire through normal research).

Of course if there's such an introduction in the wiki already and I've missed it please let me know!

Thanks once again for your patience in guiding a slow learner out of the fog... :D

Cheers!
-- Spannerbag
SP Campaigns: After EI (v1.14) Leafsea Burning (v1.18, v1.16)
I suspect the universe is simpler than we think and stranger than we can know.
Also, I fear that beyond a certain point more intelligence does not necessarily benefit a species...
User avatar
Spannerbag
Posts: 777
Joined: December 18th, 2016, 6:14 pm
Location: Yes

Re: Another basic lua query

Post by Spannerbag »

gnombat wrote: June 22nd, 2025, 3:31 pm
Spannerbag wrote: June 22nd, 2025, 10:56 am Put comma after every parameter (except the last one of course) to tell lua where each one ends.
Actually, you can put a comma after the last one if you want; Lua will allow a trailing comma in a table constructor.

(This only works for tables - Lua doesn't allow a trailing comma in other places like function calls.)
Ah, OK,another little wrinkle to remember! :)

Thanks for the heads-up.

Cheers!
-- Spannerbag
SP Campaigns: After EI (v1.14) Leafsea Burning (v1.18, v1.16)
I suspect the universe is simpler than we think and stranger than we can know.
Also, I fear that beyond a certain point more intelligence does not necessarily benefit a species...
Post Reply