Elvish_Hunter's Lua thread
Moderator: Forum Moderators
- Elvish_Hunter
- Posts: 1575
- Joined: September 4th, 2009, 2:39 pm
- Location: Lintanir Forest...
Re: Elvish_Hunter's Lua thread
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.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).
Anyway, here there is a new version:
Spoiler:
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]
- 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)
The Sojournings of Grog, Children of Dragons, A Rough Life, Wesnoth Lua Pack, The White Troll (co-author)
Re: Elvish_Hunter's Lua thread
I don't understand the question. Do you mean that invisible enemy units should be visible to your pathfinding?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?
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:- 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?
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: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?
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: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)?
Not yet.Elvish_Hunter wrote:Just to know, the read-only experience field was fixed?
- Elvish_Hunter
- Posts: 1575
- Joined: September 4th, 2009, 2:39 pm
- Location: Lintanir Forest...
Re: Elvish_Hunter's Lua thread
Yes, it was. However, I talked via PM with Sapient, and this was his answer:silene wrote:I don't understand the question. Do you mean that invisible enemy units should be visible to your pathfinding?
So, it isn't really necessary, but that was the original idea.Sapient wrote:That's unfortunate but no great loss. Hopefully ignore_ambush=no can be added to the pathfinding engine at some point in the future.Elvish_Hunter wrote: - ignore_ambush key cannot be implemented because the pathfinding engine cannot take ambushes in account
Thanks for the clarification: such value is useful for one of my testings in the code above.silene wrote:The 4242... is normal, it's a value hardcoded in the engine to mean unreachable. The other one just derives from it.
Acknowledged. While developing the tag above I found myself wondering "how much time required its execution?", and imagined that a code likesilene 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.
Code: Select all
a = time.clock()
-- code here
b = time.clock()
wesnoth.message("Execution time: ", tostring( b - a ))
OK. I'll update to the latest SVN, recompile, test and file a proper bug report.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.
After filing the above, I'll file a feature request for this, just as reminder.silene wrote:Elvish_Hunter wrote:
Just to know, the read-only experience field was fixed?
Not yet.
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)
The Sojournings of Grog, Children of Dragons, A Rough Life, Wesnoth Lua Pack, The White Troll (co-author)
-
- Inactive Developer
- Posts: 2461
- Joined: August 15th, 2008, 8:46 pm
- Location: Germany
Re: Elvish_Hunter's Lua thread
For now use [set_variable]time=stamp for it.Elvish_Hunter wrote:Code: Select all
a = time.clock() -- code here b = time.clock() wesnoth.message("Execution time: ", tostring( b - a ))
projects (BfW 1.12):
A Simple Campaign: campaign draft for wml starters • Plan Your Advancements: mp mod
The Earth's Gut: sp campaign • Settlers of Wesnoth: mp scenario • Wesnoth Lua Pack: lua tags and utils
updated to 1.8 and handed over: A Gryphon's Tale: sp campaign
A Simple Campaign: campaign draft for wml starters • Plan Your Advancements: mp mod
The Earth's Gut: sp campaign • Settlers of Wesnoth: mp scenario • Wesnoth Lua Pack: lua tags and utils
updated to 1.8 and handed over: A Gryphon's Tale: sp campaign
- Elvish_Hunter
- Posts: 1575
- Joined: September 4th, 2009, 2:39 pm
- Location: Lintanir Forest...
Re: Elvish_Hunter's Lua thread
A thing that I didn't remembered, because I never used it before. Thanks.Anonymissimus wrote:For now use [set_variable]time=stamp for it.
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:Its usage as WML tag is: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 output is a WML array with the following values for each unit: id, x, y, terrain and defense.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]
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)
The Sojournings of Grog, Children of Dragons, A Rough Life, Wesnoth Lua Pack, The White Troll (co-author)
- Elvish_Hunter
- Posts: 1575
- Joined: September 4th, 2009, 2:39 pm
- Location: Lintanir Forest...
Re: Elvish_Hunter's Lua thread
As promised, with the experience field fixed and wesnoth.transform_unit documented, I restarted working on [transform_unit]. Here there is its second version: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:
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?
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
{TRANSFORM_UNIT (side=1) () }
the wiki says:
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?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.
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)
The Sojournings of Grog, Children of Dragons, A Rough Life, Wesnoth Lua Pack, The White Troll (co-author)
Re: Elvish_Hunter's Lua thread
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.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?
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.
- Elvish_Hunter
- Posts: 1575
- Joined: September 4th, 2009, 2:39 pm
- Location: Lintanir Forest...
Re: Elvish_Hunter's Lua thread
The other options were: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.
- 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
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)
The Sojournings of Grog, Children of Dragons, A Rough Life, Wesnoth Lua Pack, The White Troll (co-author)
Re: Elvish_Hunter's Lua thread
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.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.
- Elvish_Hunter
- Posts: 1575
- Joined: September 4th, 2009, 2:39 pm
- Location: Lintanir Forest...
Re: Elvish_Hunter's Lua thread
OK. I uploaded the [transform_unit] patch on Gna: https://gna.org/patch/index.php?2392 .
And now... [harm_unit] ... the vengeance!
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:and code here, with my comments: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:cost is returned by find_path, and are the MP required to the unit to reach the location.
And now... [harm_unit] ... the vengeance!
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]
Spoiler:
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 )
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)
The Sojournings of Grog, Children of Dragons, A Rough Life, Wesnoth Lua Pack, The White Troll (co-author)
-
- Inactive Developer
- Posts: 2461
- Joined: August 15th, 2008, 8:46 pm
- Location: Germany
Re: Elvish_Hunter's Lua thread
I suggest not working on it until shadowmaster specifies what he wants to have there.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?
projects (BfW 1.12):
A Simple Campaign: campaign draft for wml starters • Plan Your Advancements: mp mod
The Earth's Gut: sp campaign • Settlers of Wesnoth: mp scenario • Wesnoth Lua Pack: lua tags and utils
updated to 1.8 and handed over: A Gryphon's Tale: sp campaign
A Simple Campaign: campaign draft for wml starters • Plan Your Advancements: mp mod
The Earth's Gut: sp campaign • Settlers of Wesnoth: mp scenario • Wesnoth Lua Pack: lua tags and utils
updated to 1.8 and handed over: A Gryphon's Tale: sp campaign
- Elvish_Hunter
- Posts: 1575
- Joined: September 4th, 2009, 2:39 pm
- Location: Lintanir Forest...
Re: Elvish_Hunter's Lua thread
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:
I also noticed this, again in FutureWML:
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].* [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
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)
The Sojournings of Grog, Children of Dragons, A Rough Life, Wesnoth Lua Pack, The White Troll (co-author)
Re: Elvish_Hunter's Lua thread
No, store_locations involves no user input or interaction.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].
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."
Re: Elvish_Hunter's Lua thread
It seems more natural to me to animate the attacker for each target unit. Not sure about the optional attribute though.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?
Author of the unofficial UtBS sequels Invasion from the Unknown and After the Storm.
- Elvish_Hunter
- Posts: 1575
- Joined: September 4th, 2009, 2:39 pm
- Location: Lintanir Forest...
Re: Elvish_Hunter's Lua thread
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]).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.
Thanks. I added these lines to the tag, after the start of the harming cycle:shadowmaster wrote:It seems more natural to me to animate the attacker for each target unit.
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
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.shadowmaster wrote:Not sure about the optional attribute though.
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]
( {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]
- 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)
The Sojournings of Grog, Children of Dragons, A Rough Life, Wesnoth Lua Pack, The White Troll (co-author)