1local LS = wesnoth.require "location_set"
2local AH = wesnoth.require "ai/lua/ai_helper.lua"
3
4local function get_lurker(cfg)
5    -- We simply pick the first of the lurkers, they have no strategy
6    local lurker = AH.get_units_with_moves {
7        side = wesnoth.current.side,
8        { "and", wml.get_child(cfg, "filter") }
9    }[1]
10    return lurker
11end
12
13local ca_lurkers = {}
14
15function ca_lurkers:evaluation(cfg)
16    if get_lurker(cfg) then return cfg.ca_score end
17    return 0
18end
19
20function ca_lurkers:execution(cfg)
21    local lurker = get_lurker(cfg)
22    local targets = AH.get_attackable_enemies()
23
24    -- Sort targets by hitpoints (lurkers choose lowest HP target)
25    table.sort(targets, function(a, b) return (a.hitpoints < b.hitpoints) end)
26
27    local reach = LS.of_pairs(wesnoth.find_reach(lurker.x, lurker.y))
28    local lurk_area = wml.get_child(cfg, "filter_location")
29    local reachable_attack_terrain =
30         LS.of_pairs(wesnoth.get_locations  {
31            { "and", { x = lurker.x, y = lurker.y, radius = lurker.moves } },
32            { "and", lurk_area }
33        })
34    reachable_attack_terrain:inter(reach)
35
36    -- Need to restrict that to reachable and not occupied by an ally (except own position)
37    local reachable_attack_terrain = reachable_attack_terrain:filter(function(x, y, v)
38        local occ_hex = AH.get_visible_units(wesnoth.current.side, {
39            x = x, y = y,
40            { "not", { x = lurker.x, y = lurker.y } }
41        })[1]
42        return not occ_hex
43    end)
44
45    -- Attack the weakest reachable enemy
46    for _,target in ipairs(targets) do
47        -- Get reachable attack terrain next to target unit
48        local reachable_attack_terrrain_adj_target = LS.of_pairs(
49            wesnoth.get_locations { x = target.x, y = target.y, radius = 1 }
50        )
51        reachable_attack_terrrain_adj_target:inter(reachable_attack_terrain)
52
53        -- Since enemies are sorted by hitpoints, we can simply attack the first enemy found
54        if reachable_attack_terrrain_adj_target:size() > 0 then
55            local rand = math.random(1, reachable_attack_terrrain_adj_target:size())
56            local dst = reachable_attack_terrrain_adj_target:to_stable_pairs()
57
58            AH.robust_move_and_attack(ai, lurker, dst[rand], target)
59            return
60       end
61    end
62
63    -- If we got here, unit did not attack: go to random wander terrain hex
64    if (lurker.moves > 0) and (not cfg.stationary) then
65        local reachable_wander_terrain =
66            LS.of_pairs( wesnoth.get_locations {
67                { "and", { x = lurker.x, y = lurker.y, radius = lurker.moves } },
68                { "and", wml.get_child(cfg, "filter_location_wander") or lurk_area }
69            })
70        reachable_wander_terrain:inter(reach)
71
72        -- Need to restrict that to reachable and not occupied by an ally (except own position)
73        local reachable_wander_terrain = reachable_wander_terrain:filter(function(x, y, v)
74            local occ_hex = AH.get_visible_units(wesnoth.current.side, {
75                x = x, y = y,
76                { "not", { x = lurker.x, y = lurker.y } }
77            })[1]
78            return not occ_hex
79        end)
80
81        if (reachable_wander_terrain:size() > 0) then
82            local dst = reachable_wander_terrain:to_stable_pairs()
83            local rand = math.random(1, reachable_wander_terrain:size())
84            AH.movefull_stopunit(ai, lurker, dst[rand])
85            return
86        end
87    end
88
89    -- If the unit has moves or attacks left at this point, take them away
90    AH.checked_stopunit_all(ai, lurker)
91end
92
93return ca_lurkers
94