Elvish_Hunter's Lua thread

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

Moderator: Forum Moderators

Post Reply
User avatar
Elvish_Hunter
Posts: 1576
Joined: September 4th, 2009, 2:39 pm
Location: Lintanir Forest...

Re: Elvish_Hunter's Lua thread

Post by Elvish_Hunter »

Anonymissimus wrote:Quote:
- I noticed that, if max_cost has a numeric value, like 5, and all available paths go above this value, the following values are assigned with my code:

It's probably the C++ equivalent for math.huge or something. Maybe better than nothing or nil or false in this case (when there can be no path).
I tried assigning to a WML variable the math.huge value, and I obtained a variable with an "inf" value. Not a problem: in fact, I used this feature to avoid storing the variables and calculating the path if no path can be found.
Anyway, here there is a new version:
Spoiler:
Its usage should be:

Code: Select all

[find_path]
DISCUSSION: here
    [traveler] 
        # SUF, only the first unit matching the filter will be used
    [/traveler]
    [destination]
        # SLF, the matching location with the lowest distance and the lowest movement cost will be used
    [/destination]
    variable= # default "path", here all the steps of the path will be stored, with a summary at path[0]
    allow_multiple_turns= # default "no", if yes also paths that will require more than one turn will be calculated
    ignore_units = # default no
    ignore_teleport # default no
    ignore_visibility # default yes
[/find_path]
And now, the usual bunch of questions:
- While testing this tag, I noticed that passing a border coordinate to wesnoth.find_path (like 0, 0) crashes Wesnoth (r48206) with the following message in terminal:
wesnoth: src/pathfind/astarsearch.cpp:137: pathfind::plain_route pathfind::a_star_search(const map_location&, const map_location&, double, const pathfind::cost_calculator*, size_t, size_t, const std::set<map_location, std::less<map_location>, std::allocator<map_location> >*): Asserzione "dst.valid(width, height)" non riuscita.
Aborted

(of course, error message is in Italian, and translates in English as Assertion "dst.valid(width, height)" failed.
To work around this, in the first cycle for I placed a line that checks if the location is a border one. Should I report this assertion failed (I know that WML should never cause crashes, but I don't know about Lua)?
- These lines exit the cycle without any message:
if cost >= 42424242 then return end # here I make use of the value returned by Lua
if not allow_multiple_turns and turns > 1 then return end

Considering that this tag is for core, should I add a wesnoth.message here as well?
- To calculate turns needed, for now I use local turns = math.ceil( cost / unit.max_moves ): any better solution?
- Just to know, the read-only experience field was fixed? If not, I'll report it as well.
Current maintainer of these add-ons, all on 1.16:
The Sojournings of Grog, Children of Dragons, A Rough Life, Wesnoth Lua Pack, The White Troll (co-author)
silene
Posts: 1109
Joined: August 28th, 2004, 10:02 pm

Re: Elvish_Hunter's Lua thread

Post by silene »

Elvish_Hunter wrote:- It looks like I cannot implement ignore_ambush, but to be sure I need a confirmation: there is a Lua way to make the pathfinding engine take ambushes in account while calculating paths?
I don't understand the question. Do you mean that invisible enemy units should be visible to your pathfinding?
Elvish_Hunter wrote:- I noticed that, if max_cost has a numeric value, like 5, and all available paths go above this value, the following values are assigned with my code. Is this normal?
The 4242... is normal, it's a value hardcoded in the engine to mean unreachable. The other one just derives from it.
Elvish_Hunter wrote:Finally: as we know, the os library is disabled for security reasons. However, it contains also some safe functions, that do nothing bad, apart from returning strings and numbers; I'm talking about os.clock, os.time, os.date and os.difftime. Does it have sense to enable these four functions back (maybe by placing them in a time library, to allow keeping os disabled)? I thought why this may be useful, and I found a possible reason: recently, math.random was enabled again for use by Lua AI code; for example, the numeric value returned from os.time() could be passed to math.randomseed. It may be useful also to introduce "easter eggs" in UMC campaigns, like it was recently discussed in off-topic, and probably also other reasons that I cannot yet figure. Any opinion?
Just to extend a bit on Anonymissimus' reply. Passing a time value to initialize a random seed is a reasonable usage. Using time values to benchmark Lua and WML code is a reasonable usage. (Note that, if you are in a place where WML can be used, you can delegate to [set_variable] instead.) Using them to implement easter eggs is dubious. Don't forget that WML has to be deterministic (same behavior on all the hosts and in replays too), so they can't be used directly. (Obviously, the same issue occurs with [set_variable].) As with math.random, their results could be put into a synchronized choice, which would solve the issue. To summarize, I don't mind enabling them. I just want people to keep in mind that they are a can of worms.
Elvish_Hunter wrote:To work around this, in the first cycle for I placed a line that checks if the location is a border one. Should I report this assertion failed (I know that WML should never cause crashes, but I don't know about Lua)?
This is a bug, it shouldn't cause a crash. Your fix (testing for border tiles) is the correct approach, but it should be done on the C++ side.
Elvish_Hunter wrote:Just to know, the read-only experience field was fixed?
Not yet.
User avatar
Elvish_Hunter
Posts: 1576
Joined: September 4th, 2009, 2:39 pm
Location: Lintanir Forest...

Re: Elvish_Hunter's Lua thread

Post by Elvish_Hunter »

silene wrote:I don't understand the question. Do you mean that invisible enemy units should be visible to your pathfinding?
Yes, it was. However, I talked via PM with Sapient, and this was his answer:
Sapient wrote:
Elvish_Hunter wrote: - ignore_ambush key cannot be implemented because the pathfinding engine cannot take ambushes in account
That's unfortunate but no great loss. Hopefully ignore_ambush=no can be added to the pathfinding engine at some point in the future.
So, it isn't really necessary, but that was the original idea.
silene wrote:The 4242... is normal, it's a value hardcoded in the engine to mean unreachable. The other one just derives from it.
Thanks for the clarification: such value is useful for one of my testings in the code above.
silene wrote:Just to extend a bit on Anonymissimus' reply. Passing a time value to initialize a random seed is a reasonable usage. Using time values to benchmark Lua and WML code is a reasonable usage. (Note that, if you are in a place where WML can be used, you can delegate to [set_variable] instead.) Using them to implement easter eggs is dubious. Don't forget that WML has to be deterministic (same behavior on all the hosts and in replays too), so they can't be used directly. (Obviously, the same issue occurs with [set_variable].) As with math.random, their results could be put into a synchronized choice, which would solve the issue. To summarize, I don't mind enabling them. I just want people to keep in mind that they are a can of worms.
Acknowledged. While developing the tag above I found myself wondering "how much time required its execution?", and imagined that a code like

Code: Select all

a = time.clock()
    -- code here
b = time.clock()
wesnoth.message("Execution time: ", tostring( b - a ))
could work. Yes, I think that they can be useful, and I agree with your advice "handle with care". :) Of course, for my code I imagined them being placed in a time library, also to keep even clearer that all the other os functions will remain disabled.
silene wrote:This is a bug, it shouldn't cause a crash. Your fix (testing for border tiles) is the correct approach, but it should be done on the C++ side.
OK. I'll update to the latest SVN, recompile, test and file a proper bug report.
silene wrote:Elvish_Hunter wrote:
Just to know, the read-only experience field was fixed?

Not yet.
After filing the above, I'll file a feature request for this, just as reminder. :wink:
Current maintainer of these add-ons, all on 1.16:
The Sojournings of Grog, Children of Dragons, A Rough Life, Wesnoth Lua Pack, The White Troll (co-author)
Anonymissimus
Inactive Developer
Posts: 2461
Joined: August 15th, 2008, 8:46 pm
Location: Germany

Re: Elvish_Hunter's Lua thread

Post by Anonymissimus »

Elvish_Hunter wrote:

Code: Select all

a = time.clock()
    -- code here
b = time.clock()
wesnoth.message("Execution time: ", tostring( b - a ))
For now use [set_variable]time=stamp for it.
projects (BfW 1.12):
A Simple Campaign: campaign draft for wml startersPlan Your Advancements: mp mod
The Earth's Gut: sp campaignSettlers of Wesnoth: mp scenarioWesnoth Lua Pack: lua tags and utils
updated to 1.8 and handed over: A Gryphon's Tale: sp campaign
User avatar
Elvish_Hunter
Posts: 1576
Joined: September 4th, 2009, 2:39 pm
Location: Lintanir Forest...

Re: Elvish_Hunter's Lua thread

Post by Elvish_Hunter »

Anonymissimus wrote:For now use [set_variable]time=stamp for it.
A thing that I didn't remembered, because I never used it before. Thanks. :wink:
I reported the bugs (experience field and pathfinding) on Gna some days ago, and I'll restart working on the tags above ( [transform_unit] and [find_path] ) when at least one of them will be fixed, and when wesnoth.transform_unit will be documented on the wiki.
On http://forums.wesnoth.org/viewtopic.php ... 2&start=15 I made a [get_unit_defense] tag: I'm dumping it here so I won't lose its code "in the mists of the time". ^_^
Elvish_Hunter wrote:

Code: Select all

-- to store unit defense
function wesnoth.wml_actions.get_unit_defense(cfg)
	local filter = wesnoth.get_units(cfg)
	local variable = cfg.variable or "defense"

	for index, unit in ipairs(filter) do
		local terrain = wesnoth.get_terrain ( unit.x, unit.y )
		-- it is WML defense: the lower, the better. Converted to normal defense with 100 -
		local defense = 100 - wesnoth.unit_defense ( unit, terrain )
		wesnoth.set_variable ( string.format ( "%s[%d]", variable, index - 1 ), { id = unit.id, x = unit.x, y = unit.y, terrain = terrain, defense = defense } )
	end
end
Its usage as WML tag is:

Code: Select all

[get_unit_defense]
	side=1 # SUF, don't use a [filter] tag
	variable = my_var # place here your variable name, default is "defense"
[/get_unit_defense]
Its output is a WML array with the following values for each unit: id, x, y, terrain and defense.
Current maintainer of these add-ons, all on 1.16:
The Sojournings of Grog, Children of Dragons, A Rough Life, Wesnoth Lua Pack, The White Troll (co-author)
User avatar
Elvish_Hunter
Posts: 1576
Joined: September 4th, 2009, 2:39 pm
Location: Lintanir Forest...

Re: Elvish_Hunter's Lua thread

Post by Elvish_Hunter »

As promised, with the experience field fixed and wesnoth.transform_unit documented, I restarted working on [transform_unit]. Here there is its second version:

Code: Select all

function wml_actions.transform_unit(cfg)
	-- replacement for macro TRANSFORM_UNIT
	local transform_to_type = cfg.transform_to_type or helper.wml_error("Missing required transform_to_type= in [transform_unit]")
	local filter = (helper.get_child(cfg, "filter")) or helper.wml_error("Missing required [filter] in [transform_unit]")

	for index, unit in ipairs(wesnoth.get_units(filter)) do
		if unit.valid then
			local hitpoints = unit.hitpoints -- store hitpoints

			wesnoth.transform_unit( unit, transform_to_type )

			-- restore HP, but do not go beyond maximum
			if hitpoints < unit.max_hitpoints then unit.hitpoints = hitpoints end

			-- we don't want to poison undead, do we?
			if unit.status.not_living then unit.status.poisoned = nil end
		end
	end

	wml_actions.redraw {}
end
It seems to mimic reasonably well the behavior of {TRANSFORM_UNIT}, but I have a doubt: if a scenario contains this:
{TRANSFORM_UNIT (side=1) () }
the wiki says:
TRANSFORM_UNIT FILTER TYPE

Transforms all units matching the filter into TYPE or if missing, their normal advancement Keeps the unit's hitpoints, experience and status intact.
For now, I placed a WML error if transform_to_type= is missing, but what will be the best way to handle such situation? I though about reading the unit.__cfg.advances_to= field, and doing a string.gmatch in case the unit has multiple advancements (example: Spearman), but then the problem will be how to show the advancement dialog. I also though, if transform_to_type is missing, to raise the unit's XP to maximum, store and unstore such unit, and then restoring HP, XP and status like I did in the first version. Any other suggestion?

Finally, it is just me, or when a unit advances, in 1.9.4+svn the remaining XP aren't carried anymore? Example: let's say that a unit has 49 XP out of 50, and kills a level 1. Before, it advanced, and retained 7 XP; now, it advances and starts with 0 XP (if it's interesting, I noticed it while playing Liberty). Does anybody else have it? :?
Current maintainer of these add-ons, all on 1.16:
The Sojournings of Grog, Children of Dragons, A Rough Life, Wesnoth Lua Pack, The White Troll (co-author)
silene
Posts: 1109
Joined: August 28th, 2004, 10:02 pm

Re: Elvish_Hunter's Lua thread

Post by silene »

Elvish_Hunter wrote:Finally, it is just me, or when a unit advances, in 1.9.4+svn the remaining XP aren't carried anymore? Example: let's say that a unit has 49 XP out of 50, and kills a level 1. Before, it advanced, and retained 7 XP; now, it advances and starts with 0 XP (if it's interesting, I noticed it while playing Liberty). Does anybody else have it? :?
My fault. I had forgotten about this, so when implementing the writability of unit.experience, the unit experience at leveling got reset instead of being retained. I will fix it.

As for the advancement dialog, there is no easy way to display it currently. It is possible to faithfully emulate it, but I don't think it is worth the insanity.
User avatar
Elvish_Hunter
Posts: 1576
Joined: September 4th, 2009, 2:39 pm
Location: Lintanir Forest...

Re: Elvish_Hunter's Lua thread

Post by Elvish_Hunter »

silene wrote:As for the advancement dialog, there is no easy way to display it currently. It is possible to faithfully emulate it, but I don't think it is worth the insanity.
The other options were:
- choose an advancement using helper.rand (after doing the string.gmatch thing that I said before)
EDIT: the above solution is out of question, because it won't handle AMLAs and the advances_to=null special case.
- use the old behaviour if transform_to_type is missing (currently used below)
I can easily change from one to another, if required.

Code: Select all

function wml_actions.transform_unit(cfg)
	-- replacement for macro TRANSFORM_UNIT
	local transform_to_type = cfg.transform_to_type
	local filter = (helper.get_child(cfg, "filter")) or helper.wml_error("Missing required [filter] in [transform_unit]")

	for index, unit in ipairs(wesnoth.get_units(filter)) do
		if unit.valid then
			local hitpoints = unit.hitpoints -- store hitpoints

			if transform_to_type then
				-- if we have a type, use transform_unit
				wesnoth.transform_unit( unit, transform_to_type )
			else
				-- if not, perform transformation to normal level-up
				unit.experience = unit.experience + unit.max_experience
				local status = helper.get_child( unit.__cfg, "status" )

				--because otherwise no advancement, and no advancement dialog.
				wml_actions.store_unit { { "filter", { id = unit.id } }, variable = "Lua_store_unit", kill = true }
				wml_actions.unstore_unit { variable = "Lua_store_unit", find_vacant = false, advance = true }
				wml_actions.clear_variable { name = "Lua_store_unit" }

				-- restoring status
				for key, value in pairs( status ) do unit.status[key] = value end
			end

			-- restore HP, but do not go beyond maximum
			if hitpoints < unit.max_hitpoints then unit.hitpoints = hitpoints end

			-- we don't want to poison undead, do we?
			if unit.status.not_living then unit.status.poisoned = nil end
		end
	end

	wml_actions.redraw {}
end
Only thing that I noticed is that sometime, after running wesnoth.transform_unit, the unit sprite doesn't change (even with redraw) until I select it.
Also, should I keep the currently HP behavior (do not violate maximum) or switch to the current TRANSFORM_UNIT behavior (violate maximum HP)? Any other suggestion? After that, I think that I can generate a patch and upload it on Gna.
Last edited by Elvish_Hunter on January 19th, 2011, 9:14 am, edited 1 time in total.
Current maintainer of these add-ons, all on 1.16:
The Sojournings of Grog, Children of Dragons, A Rough Life, Wesnoth Lua Pack, The White Troll (co-author)
silene
Posts: 1109
Joined: August 28th, 2004, 10:02 pm

Re: Elvish_Hunter's Lua thread

Post by silene »

Elvish_Hunter wrote:Only thing that I noticed is that sometime, after running wesnoth.transform_unit, the unit sprite doesn't change (even with redraw) until I select it.
Yes, I have noticed this glitch too. There must be some animation cache somewhere that "redraw" doesn't know about. It's on my TODO list to find it.
User avatar
Elvish_Hunter
Posts: 1576
Joined: September 4th, 2009, 2:39 pm
Location: Lintanir Forest...

Re: Elvish_Hunter's Lua thread

Post by Elvish_Hunter »

OK. I uploaded the [transform_unit] patch on Gna: https://gna.org/patch/index.php?2392 .
And now... [harm_unit] ... the vengeance! :twisted:
After StDrake's request, I made a modification to [harm_unit]. Now it calculates experience like in combat, provided that there is a secondary unit. Example of syntax:

Code: Select all

		[harm_unit]
			[filter]
				side=2
			[/filter]
			amount=10
			[filter_second]
				id=His Awesomeness
			[/filter_second]
			animate = yes
			[secondary_attack]
				name=axe
			[/secondary_attack]
			damage_type=fire
			kill=yes
			fire_event=yes
			delay=1000
			variable=damage_list
			poisoned=yes
			slowed=yes
			petrified=yes
			unhealable=no
			alignment=chaotic
		[/harm_unit]
and code here, with my comments:
Spoiler:
After a first testing, experience calculation seems to work fine (I also avoided earning XP for harming units on the same side), but the usual question is: there is something that I overlooked?
If needed, I can also follow shadowmaster's request on IRC, making [harm_unit] animating also the attacker; if I do this, I'll need to add two or three more lines of code, but the problems are: should I place the harmer animation before the whole harming cycle (animating it only once) or at the start of the harming cycle (animating it for each unit harmed?) And, in both situations, will be better if I place a key animate_attacker = to allow animating only the defenders, if required?

A question also on [find_path]: it is almost finished, I need only to know if this formula to calculate the turns required for a unit to reach a certain location is correct:

Code: Select all

local turns = math.ceil( ( ( cost - unit.moves ) / unit.max_moves ) + 1 )
cost is returned by find_path, and are the MP required to the unit to reach the location.
Current maintainer of these add-ons, all on 1.16:
The Sojournings of Grog, Children of Dragons, A Rough Life, Wesnoth Lua Pack, The White Troll (co-author)
Anonymissimus
Inactive Developer
Posts: 2461
Joined: August 15th, 2008, 8:46 pm
Location: Germany

Re: Elvish_Hunter's Lua thread

Post by Anonymissimus »

Elvish_Hunter wrote: If needed, I can also follow shadowmaster's request on IRC, making [harm_unit] animating also the attacker; if I do this, I'll need to add two or three more lines of code, but the problems are: should I place the harmer animation before the whole harming cycle (animating it only once) or at the start of the harming cycle (animating it for each unit harmed?) And, in both situations, will be better if I place a key animate_attacker = to allow animating only the defenders, if required?
I suggest not working on it until shadowmaster specifies what he wants to have there.
projects (BfW 1.12):
A Simple Campaign: campaign draft for wml startersPlan Your Advancements: mp mod
The Earth's Gut: sp campaignSettlers of Wesnoth: mp scenarioWesnoth Lua Pack: lua tags and utils
updated to 1.8 and handed over: A Gryphon's Tale: sp campaign
User avatar
Elvish_Hunter
Posts: 1576
Joined: September 4th, 2009, 2:39 pm
Location: Lintanir Forest...

Re: Elvish_Hunter's Lua thread

Post by Elvish_Hunter »

OK. I sent a PM to shadowmaster about it, so I'll be able to finish it, and prepare a patch that will include also [transform_unit] and [find_path]. Or even make three separate patches (the one for [transform_unit] is already done), depending on what's the better solution.
I also noticed this, again in FutureWML:
* [query_location] - queries a location from the user
o variable
o [filter_location]

Regarding [query_location]: this could use a better name. Suggestions are welcome --Sapient
Am I wrong, or [store_locations] does exactly the same thing? If yes, this tag could be removed from the list as well as [advance_unit].
Current maintainer of these add-ons, all on 1.16:
The Sojournings of Grog, Children of Dragons, A Rough Life, Wesnoth Lua Pack, The White Troll (co-author)
User avatar
Sapient
Inactive Developer
Posts: 4453
Joined: November 26th, 2005, 7:41 am
Contact:

Re: Elvish_Hunter's Lua thread

Post by Sapient »

Elvish_Hunter wrote:Am I wrong, or [store_locations] does exactly the same thing? If yes, this tag could be removed from the list as well as [advance_unit].
No, store_locations involves no user input or interaction.

The goal of this tag is for the user to be able to click on a target location with left mouse click, in a way that is fully integrated with the UI. It needs a special targeting cursor. Hex highlighting may or may not be relevant. This could be used to allow firing catapults at a distant target, for example.
http://www.wesnoth.org/wiki/User:Sapient... "Looks like your skills saved us again. Uh, well at least, they saved Soarin's apple pie."
User avatar
Iris
Site Administrator
Posts: 6798
Joined: November 14th, 2006, 5:54 pm
Location: Chile
Contact:

Re: Elvish_Hunter's Lua thread

Post by Iris »

Elvish_Hunter wrote:should I place the harmer animation before the whole harming cycle (animating it only once) or at the start of the harming cycle (animating it for each unit harmed?) And, in both situations, will be better if I place a key animate_attacker = to allow animating only the defenders, if required?
It seems more natural to me to animate the attacker for each target unit. Not sure about the optional attribute though. :hmm:
Author of the unofficial UtBS sequels Invasion from the Unknown and After the Storm.
User avatar
Elvish_Hunter
Posts: 1576
Joined: September 4th, 2009, 2:39 pm
Location: Lintanir Forest...

Re: Elvish_Hunter's Lua thread

Post by Elvish_Hunter »

Sapient wrote:No, store_locations involves no user input or interaction.

The goal of this tag is for the user to be able to click on a target location with left mouse click, in a way that is fully integrated with the UI. It needs a special targeting cursor. Hex highlighting may or may not be relevant. This could be used to allow firing catapults at a distant target, for example.
It looks me a thing that must be done in C++, that I don't know. The only Lua option that comes to my mind is creating a right-click menu with wesnoth.wml_actions.set_menu_item (that in WML is [set_menu_item]).
shadowmaster wrote:It seems more natural to me to animate the attacker for each target unit.
Thanks. :) I added these lines to the tag, after the start of the harming cycle:

Code: Select all

if animate and harmer and harmer.valid then -- the harmer could be acquired by get_units, but may not be valid
				wesnoth.scroll_to_tile(harmer.x, harmer.y, true)
				wml_actions.animate_unit({ flag = "attack", hits = true, { "filter", { id = harmer.id } },
					{ "primary_attack", helper.get_child(cfg, "primary_attack") }, with_bars = true })
			end
shadowmaster wrote:Not sure about the optional attribute though. :hmm:
I asked because, after StDrake's suggestion, the tag will calculate also the experience like in regular combat, if there is an harmer. I can always modify it later.

Here, as promised, there is the cumulative patch for [harm_unit], [find_path] and [transform_unit] (with all my comments, that will be removed later).
In [harm_unit], as well as adding animation for an attacker and experience calculation, I took care of the facing of animated units. In [find_path], I decided to go for a variable format like this:

Code: Select all

[path]
    movement_cost=50
    [step]
        movement_cost=2
    [/step]
[/path]
This because it takes less space in the inspection window, and IMO it will be easier to cycle with FOREACH
( {FOREACH path.step i} ).
Syntax for these tags is already in this page (with [harm_unit] supporting also [primary_attack] to choose the animation used by the harmer), except for [transform_unit] that can be used this way:

Code: Select all

[transform_unit]
		[filter]
			 side=1
		[/filter]
		transform_to=Goblin Spearman
	[/transform_unit]
Please test these tags (I already did it on my own) and report if there is any problem / suggestion / butter cookies / whatever or if they work fine. :)
Attachments
harm_unit-find_path-transform_unit.patch
(10.11 KiB) Downloaded 352 times
Current maintainer of these add-ons, all on 1.16:
The Sojournings of Grog, Children of Dragons, A Rough Life, Wesnoth Lua Pack, The White Troll (co-author)
Post Reply