Order of AI moves

The place to post your WML questions and answers.

Moderator: Forum Moderators

Forum rules
  • Please use [code] BBCode tags in your posts for embedding WML snippets.
  • To keep your code readable so that others can easily help you, make sure to indent it following our conventions.
Post Reply
vghetto
Posts: 755
Joined: November 2nd, 2019, 5:12 pm

Order of AI moves

Post by vghetto »

Hi,

I have an AI side with nine units.
I would like to make a specific unit out of the nine be the last one to move, always. How can it be done?
User avatar
Lord-Knightmare
Discord Moderator
Posts: 2340
Joined: May 24th, 2010, 5:26 pm
Location: Somewhere in the depths of Irdya, gathering my army to eventually destroy the known world.
Contact:

Re: Order of AI moves

Post by Lord-Knightmare »

vghetto wrote: January 18th, 2022, 10:32 pm Hi,

I have an AI side with nine units.
I would like to make a specific unit out of the nine be the last one to move, always. How can it be done?
Maybe assign the CA score of that said unit with the lowest value? like 50? That unit will be at the bottom of the move list then.
Creator of "War of Legends"
Creator of the Isle of Mists survival scenario.
Maintainer of Forward They Cried
User:Knyghtmare | My Medium
vghetto
Posts: 755
Joined: November 2nd, 2019, 5:12 pm

Re: Order of AI moves

Post by vghetto »

That doesn't work. It executed the move CA which is at 20,000 instead.

Here is the example that I'm working with. I want Peasant_4 to be the last one to move.

(The mod should be call Bug_Tester for the path to work)

Code: Select all

[campaign]
    id=Tester
    rank=1
    icon="units/human-magi/red-mage-attack-magic-2.png~RC(magenta>red)"
    image="story/landscape-coast.jpg~SCALE(360,270)"
    name= _ "Tester ai order"
    abbrev= _ "TEST"
    first_scenario=The_Scenario
    define=CAMPAIGN_TESTER
[/campaign]

#ifdef CAMPAIGN_TESTER
[lua]
    code= <<
local helper = wesnoth.require "lua/helper.lua"
local LS = wesnoth.require "location_set"
local utils = wesnoth.require "wml-utils"
>>
[/lua]

[scenario]
    name= _ "The Scenario"
    id=The_Scenario

    map_data="Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg
Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg
Gg, Gg, Xu, Xu, Xu, Xu, Xu, Xu, Xu, Xu, Xu, Xu, Xu, Xu, Xu, Xu, Xu, Xu, Xu, Xu, Xu, Gg
Gg, Gg, Xu, 2 Gg, Xu, Gg, Xu, Gg, Xu, Gg, Xu, Gg, Xu, Gg, Xu, Gg, Xu, Gg, Xu, Gg, Xu, Gg
Gg, Gg, Xu, Gg, Xu, Gg, Xu, Gg, Xu, Gg, Xu, Gg, Xu, Gg, Xu, Gg, Xu, Gg, Xu, Gg, Xu, Gg
Gg, Gg, Xu, Gg, Xu, Gg, Xu, Gg, Xu, Gg, Xu, Gg, Xu, Gg, Xu, Gg, Xu, Gg, Xu, Gg, Xu, Gg
Gg, Gg, Xu, Gg, Xu, Gg, Xu, Gg, Xu, Gg, Xu, Gg, Xu, Gg, Xu, Gg, Xu, Gg, Xu, Gg, Xu, Gg
Gg, Gg, Xu, Gg, Xu, Gg, Xu, Gg, Xu, Gg, Xu, Gg, Xu, Gg, Xu, Gg, Xu, Gg, Xu, Gg, Xu, Gg
Gg, Gg, Xu, Gg, Xu, Gg, Xu, Gg, Xu, Gg, Xu, Gg, Xu, Gg, Xu, Gg, Xu, Gg, Xu, Gg, Xu, Gg
Gg, Gg, Xu, Gg, Xu, Gg, Xu, Gg, Xu, Gg, Xu, Gg, Xu, Gg, Xu, Gg, Xu, Gg, Xu, Gg, Xu, Gg
Gg, Gg, Xu, Gg, Xu, Gg, Xu, Gg, Xu, Gg, Xu, Gg, Xu, Gg, Xu, Gg, Xu, Gg, Xu, Gg, Xu, Gg
Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg
Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg
Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, 1 Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg
Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg
Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg
Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg
Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, 3 Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg
Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg
Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg"

    {DEFAULT_MUSIC_PLAYLIST}
    {DEFAULT_SCHEDULE}

    next_scenario=null
    victory_when_enemies_defeated=no

    turns = -1

    [side]
        side=1
        type=Peasant
        id=Harry
        team_name=allies
        user_team_name=allies
        suppress_end_turn_confirmation = yes
        controller=human
        recruit=
        gold=500
    [/side]

    [side]
        side=2
        controller=ai
        no_leader=yes
        team_name=badguys
        user_team_name=badguys
        recruit=
        gold=500
        [ai]
            {MODIFY_AI_ADD_CANDIDATE_ACTION 1 main_loop (
                [candidate_action]
                    engine=lua
                    name=return_guardian_bob
                    id=return_guardian_bob
                    max_score=20010
                    location="~add-ons/Bug_Tester/return_guardian.lua"
                    [args]
                        id=Peasant_4
                        return_x=9
                        return_y=3
                    [/args]
                    # In the older syntax, the above would have looked like this:
                    #eval_parms="id = 'Peasant_4', return_x = 9, return_y = 3"
                    #exec_parms="id = 'Peasant_4', return_x = 9, return_y = 3"
                [/candidate_action]
            )}
        [/ai]
    [/side]

    [side]
        side=3
        controller=ai
        no_leader=yes
        team_name=allies
        user_team_name=allies
        [ai]
            ai_algorithm=idle_ai
        [/ai]
        recruit=
        gold=500
    [/side]

    [event]
        name=prestart

#define MY_UNIT SIDE TYPE X Y WML
    [label]
        x={X}
        y={Y}
        text={WML}
    [/label]
    [unit]
        side={SIDE}
        type={TYPE}
        x={X}
        y={Y}
        random_traits=no
        random_gender=no
        generate_name=no
        name={WML}
        id={WML}
    [/unit]
#enddef

        {MY_UNIT 2 Peasant 3 4 Peasant_1}
        {MY_UNIT 2 Peasant 5 4 Peasant_2}
        {MY_UNIT 2 Peasant 7 4 Peasant_3}
        {MY_UNIT 2 Peasant 9 4 Peasant_4}
        {MY_UNIT 2 Peasant 11 4 Peasant_5}
        {MY_UNIT 2 Peasant 13 4 Peasant_6}
        {MY_UNIT 2 Peasant 15 4 Peasant_7}
        {MY_UNIT 2 Peasant 17 4 Peasant_8}
        {MY_UNIT 2 Peasant 19 4 Peasant_9}
    [/event]
[/scenario]
#endif

Code: Select all

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

local function get_guardian(cfg)
    local filter = cfg.filter or { id = cfg.id }
    local guardian = AH.get_units_with_moves {
        side = wesnoth.current.side,
        { "and", filter }
    }[1]

    return guardian
end

local ca_return_guardian = {}

-- params for eval_parms (ai, cfg, self)
function ca_return_guardian:evaluation(cfg, data, filter_own)
	std_print("evaluation")
    local guardian = get_guardian(cfg)
    if guardian then
--	local filter = cfg.filter or { id = cfg.id }
--	local other_units = AH.get_units_with_moves {
--		side = wesnoth.current.side,
--        	{ "not", filter }
--	}[1]
--
--	if other_units then
--		std_print("Return is 15000")
--		return 15000
--	end

        if (guardian.x == cfg.return_x) and (guardian.y == cfg.return_y) then
	std_print("Return is 19990")
            return 19990
        else
	std_print("Return is 20010")
            return 20010
        end
    end

	std_print("Return is 0")
    return 0
end

-- params for exec_parms (ai, cfg, self)
function ca_return_guardian:execution(cfg, data, filter_own)
	std_print("excution")
    local guardian = get_guardian(cfg)

--    	local filter = cfg.filter or { id = cfg.id }
--	local other_units = AH.get_units_with_moves {
--		side = wesnoth.current.side,
--        	{ "not", filter }
--	}[1]
--
--	if other_units then
--		std_print("Exceution return")
--		return
--	end

    -- In case the return hex is occupied:
    local x, y = cfg.return_x, cfg.return_y
    if (guardian.x ~= x) or (guardian.y ~= y) then
        x, y = wesnoth.find_vacant_tile(x, y, guardian)
    end

    local next_hop = AH.next_hop(guardian, x, y)
    if (not next_hop) then next_hop = { guardian.x, guardian.y } end

    if ((next_hop[1] ~= guardian.x) or (next_hop[2] ~= guardian.y)) then
        ai.move_full(guardian, next_hop[1], next_hop[2])
    else
        ai.stopunit_moves(guardian)
    end
end

return ca_return_guardian
vghetto
Posts: 755
Joined: November 2nd, 2019, 5:12 pm

Re: Order of AI moves

Post by vghetto »

The solution is hiding somewhere in the hang out micro ai (or maybe the healer micro ai). I need to take away the moves and give them back when other_units returns nil. I still haven't figured out how to do it.

Edit: Holy cr*p it worked!
I'll post the working version soon.
vghetto
Posts: 755
Joined: November 2nd, 2019, 5:12 pm

Re: Order of AI moves

Post by vghetto »

Here is a working version. Peasant_4 will always be the last one to move/attack.
I don't know if what I'm doing would invalidate the game state.
There could be other problems with it. There might be an infinite loop happening somewhere :/
It still needs a lot of work to get it right.
It wouldn't be a bad idea if mainline had a micro_ai with a similar concept.

Code: Select all

[campaign]
    id=Tester
    rank=1
    icon="units/human-magi/red-mage-attack-magic-2.png~RC(magenta>red)"
    image="story/landscape-coast.jpg~SCALE(360,270)"
    name= _ "Tester ai order"
    abbrev= _ "TEST"
    first_scenario=The_Scenario
    define=CAMPAIGN_TESTER
[/campaign]

#ifdef CAMPAIGN_TESTER
[lua]
    code= <<
local helper = wesnoth.require "lua/helper.lua"
local LS = wesnoth.require "location_set"
local utils = wesnoth.require "wml-utils"
>>
[/lua]

[scenario]
    name= _ "The Scenario"
    id=The_Scenario

    map_data="Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg
Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg
Gg, Gg, Xu, Xu, Xu, Xu, Xu, Xu, Xu, Xu, Xu, Xu, Xu, Xu, Xu, Xu, Xu, Xu, Xu, Xu, Xu, Gg
Gg, Gg, Xu, 2 Gg, Xu, Gg, Xu, Gg, Xu, Gg, Xu, Gg, Xu, Gg, Xu, Gg, Xu, Gg, Xu, Gg, Xu, Gg
Gg, Gg, Xu, Gg, Xu, Gg, Xu, Gg, Xu, Gg, Xu, Gg, Xu, Gg, Xu, Gg, Xu, Gg, Xu, Gg, Xu, Gg
Gg, Gg, Xu, Gg, Xu, Gg, Xu, Gg, Xu, Gg, Xu, Gg, Xu, Gg, Xu, Gg, Xu, Gg, Xu, Gg, Xu, Gg
Gg, Gg, Xu, Gg, Xu, Gg, Xu, Gg, Xu, Gg, Xu, Gg, Xu, Gg, Xu, Gg, Xu, Gg, Xu, Gg, Xu, Gg
Gg, Gg, Xu, Gg, Xu, Gg, Xu, Gg, Xu, Gg, Xu, Gg, Xu, Gg, Xu, Gg, Xu, Gg, Xu, Gg, Xu, Gg
Gg, Gg, Xu, Gg, Xu, Gg, Xu, Gg, Xu, Gg, Xu, Gg, Xu, Gg, Xu, Gg, Xu, Gg, Xu, Gg, Xu, Gg
Gg, Gg, Xu, Gg, Xu, Gg, Xu, Gg, Xu, Gg, Xu, Gg, Xu, Gg, Xu, Gg, Xu, Gg, Xu, Gg, Xu, Gg
Gg, Gg, Xu, Gg, Xu, Gg, Xu, Gg, Xu, Gg, Xu, Gg, Xu, Gg, Xu, Gg, Xu, Gg, Xu, Gg, Xu, Gg
Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg
Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg
Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, 1 Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg
Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg
Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg
Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg
Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, 3 Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg
Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg
Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg"

    {DEFAULT_MUSIC_PLAYLIST}
    {DEFAULT_SCHEDULE}

    next_scenario=null
    victory_when_enemies_defeated=no

    turns = -1

    [side]
        side=1
        type=Peasant
        id=Harry
        team_name=allies
        user_team_name=allies
        suppress_end_turn_confirmation = yes
        controller=human
        recruit=
        gold=500
        [status]
            invulnerable=yes
        [/status]
    [/side]

    [side]
        side=2
        controller=ai
        no_leader=yes
        team_name=badguys
        user_team_name=badguys
        recruit=
        gold=500
        [ai]
            {MODIFY_AI_ADD_CANDIDATE_ACTION 1 main_loop (
                [candidate_action]
                    engine=lua
                    name=move_last_bob
                    id=move_last_bob
                    max_score=300000
                    location="~add-ons/Bug_Tester/move_last.lua"
                    [args]
                        ai_id=dummy_move_last
                        id=Peasant_4
                    [/args]
                    # In the older syntax, the above would have looked like this:
                    #eval_parms="ai_id = 'dummy_move_last', id = 'Peasant_4'"
                    #exec_parms="ai_id = 'dummy_move_last', id = 'Peasant_4'"
                [/candidate_action]
            )}
        [/ai]
    [/side]

    [side]
        side=3
        controller=ai
        no_leader=yes
        team_name=allies
        user_team_name=allies
        [ai]
            ai_algorithm=idle_ai
        [/ai]
        recruit=
        gold=500
    [/side]

    [event]
        name=prestart

#define MY_UNIT SIDE TYPE X Y WML
    [label]
        x={X}
        y={Y}
        text={WML}
    [/label]
    [unit]
        side={SIDE}
        type={TYPE}
        x={X}
        y={Y}
        random_traits=no
        random_gender=no
        generate_name=no
        name={WML}
        id={WML}
    [/unit]
#enddef

        {MY_UNIT 2 Peasant 3 4 Peasant_1}
        {MY_UNIT 2 Peasant 5 4 Peasant_2}
        {MY_UNIT 2 Peasant 7 4 Peasant_3}
        {MY_UNIT 2 Peasant 9 4 Peasant_4}
        {MY_UNIT 2 Peasant 11 4 Peasant_5}
        {MY_UNIT 2 Peasant 13 4 Peasant_6}
        {MY_UNIT 2 Peasant 15 4 Peasant_7}
        {MY_UNIT 2 Peasant 17 4 Peasant_8}
        {MY_UNIT 2 Peasant 19 4 Peasant_9}
    [/event]
[/scenario]
#endif

Code: Select all

local AH = wesnoth.require "ai/lua/ai_helper.lua"
local MAIUV = wesnoth.require "ai/micro_ais/micro_ai_unit_variables.lua"

local function get_last_unit(cfg)
	local filter = cfg.filter or { id = cfg.id }
	local last_unit = AH.get_units_with_moves {
		side = wesnoth.current.side,
		{ "and", filter }
	}[1]

	return last_unit
end

local function get_last_unit_frozen(cfg)
	local filter = cfg.filter or { id = cfg.id }
	local all_units = wesnoth.get_units {
		side = wesnoth.current.side,
		{ "and", filter }
	}

	local units = {}
	for _,unit in ipairs(all_units) do
            if MAIUV.get_mai_unit_variables(unit, cfg.ai_id, "frozen") then
                table.insert(units, unit)
            end
        end

	return units[1]
end

local function get_other_unit(cfg)
	local filter = cfg.filter or { id = cfg.id }
	local other_unit = AH.get_units_with_moves {
		side = wesnoth.current.side,
		{ "not", filter }
	}[1]

	return other_unit
end

local ca_move_last = {}

-- params for eval_parms (ai, cfg, self)
function ca_move_last:evaluation(cfg, data, filter_own)
	std_print("evaluation")

	local last_unit_frozen = get_last_unit_frozen(cfg)
	local last_unit = get_last_unit(cfg)

	local filter = cfg.filter or { id = cfg.id }
	local other_unit = get_other_unit(cfg)

	if not other_unit then
		if last_unit_frozen then
			std_print("evaluation: return 300000 not other_units")
			return 300000
		end
	end

	local last_unit = get_last_unit(cfg)
	if last_unit then
		std_print("evaluation: return 300000 last_unit")
		return 300000
	end
	std_print("evaluation: return 0 no last_unit end of function")
	return 0
end

-- params for exec_parms (ai, cfg, self)
function ca_move_last:execution(cfg, data, filter_own)
	std_print("execution")

	local last_unit = get_last_unit(cfg)
	local other_unit = get_other_unit(cfg)

	if other_unit and last_unit then
		MAIUV.set_mai_unit_variables(last_unit, cfg.ai_id, { frozen = true, moves = last_unit.moves, attacks_left = last_unit.attacks_left })
		AH.checked_stopunit_all(ai, last_unit)
		std_print("execution: Freezing last_unit and return")
		return
	end

	last_unit = get_last_unit_frozen(cfg)
	if last_unit then
		last_unit.moves = MAIUV.get_mai_unit_variables(last_unit, cfg.ai_id, "moves" )
		last_unit.attacks_left = MAIUV.get_mai_unit_variables(last_unit, cfg.ai_id, "attacks_left" )
		MAIUV.delete_mai_unit_variables(last_unit, cfg.ai_id)
	end
	std_print("execution: fallthru last_unit to other CAs")
end

return ca_move_last
Edit: It appears I need to freeze all and thaw all whenever the execution happens because otherwise it would miss some.
I also needed to restore the resting status of the unit. Without it the frozen units healed 2 HP when the moves and attacks_left were given back.

For the [micro_ai] version, you can see it in my Wild_Frontiers repo.
ca_wf_move_last.lua
mai definition
usage

There are other micro_ais worth checking too :)
User avatar
Celtic_Minstrel
Developer
Posts: 2166
Joined: August 3rd, 2012, 11:26 pm
Location: Canada
Contact:

Re: Order of AI moves

Post by Celtic_Minstrel »

I think you can do this without any Lua code. Just include two copies of the standard movement CA, one with a [filter_own][not]id=Peasant_4 that has the standard score, and one with a [filter_own]id=Peasant_4 that has a lower score than any other CA.
Author of The Black Cross of Aleron campaign and Default++ era.
Former maintainer of Steelhive.
vghetto
Posts: 755
Joined: November 2nd, 2019, 5:12 pm

Re: Order of AI moves

Post by vghetto »

I'm still trying to get the micro_ai version to work right, but I'm having issues with the rest healing.
The healing is happening in a strange way.
Here's the setup, I have 9 peasants. They all start with their hitpoints set to 8.
The paths for Peasants 2 and 8 are blocked, so they shouldn't be able to move at all.
Peasants 4 and 6 are controlled by the move_last micro_ai.

Now what I can't understand or fix is that on turn 3 Peasants 2,8 and 4,6 start to rest heal by 2 points.
Why didn't peasants 2,8 rest heal on turn 2?
Why are peasants 4,6 rest healing even though they have moved and their resting flag should be false?

I'm testing this on 1.14.
Anyone knows what's going on here? (Keep moving your peasant back if you want to test it :))

And a final question, the last_move micro ai seems to be getting blacklisted. Is there a way to force change the gamestate without having to move the units or take away their attacks?
Celtic_Minstrel wrote: January 20th, 2022, 12:32 am I think you can do this without any Lua code. Just include two copies of the standard movement CA, one with a [filter_own][not]id=Peasant_4 that has the standard score, and one with a [filter_own]id=Peasant_4 that has a lower score than any other CA.
Thanks. I'll give it a try later. Will it work on 1.14?
Attachments
Bug_Tester.zip
(3.04 KiB) Downloaded 41 times
mattsc
Inactive Developer
Posts: 1217
Joined: October 13th, 2010, 6:14 pm

Re: Order of AI moves

Post by mattsc »

vghetto wrote: January 24th, 2022, 1:10 pm And a final question, the last_move micro ai seems to be getting blacklisted. Is there a way to force change the gamestate without having to move the units or take away their attacks?
There is no "built-in" way of doing this, but you can make it happen by taking the moves (or attacks) away and then giving them back:

Code: Select all

function utils.force_gamestate_change(ai)
    -- Can be done using any unit of the AI side; works even if the unit already has 0 moves
    local unit = wesnoth.units.find_on_map { side = wesnoth.current.side }[1]
    local cfg_reset_moves = { id = unit.id, moves = unit.moves }
    ai.stopunit_moves(unit)
    wesnoth.sync.invoke_command('reset_moves', cfg_reset_moves)
end
with

Code: Select all

function wesnoth.custom_synced_commands.reset_moves(cfg)
    local unit = wesnoth.units.find_on_map { id = cfg.id }[1]
    unit.moves = cfg.moves
end
I assume it is obvious that you have to be really careful with something like this so that the AI does not get caught in an infinite loop.
vghetto wrote: January 24th, 2022, 1:10 pm Thanks. I'll give it a try later. Will it work on 1.14?
No, this was introduced in 1.15.3.
vghetto
Posts: 755
Joined: November 2nd, 2019, 5:12 pm

Re: Order of AI moves

Post by vghetto »

Hi mattsc, it's good to see you again!
Thanks, that was super helpful! It's good to know that this method "works even if the unit already has 0 moves".
I didn't know that before.
For my case, blacklisting would be the safest bet, but I will try to refactor the code to use the force_gamestate_change in another version. I would have to be real careful with the evaluation to avoid an infinite loop.

I solved in a rudimentary way the rest healing problem by saving the last_x and last_y and setting the resting flag to false if they have changed. It's not perfect, it doesn't take into account attacks_left.

I still don't get why they flipped to resting=true by default for the micro_ai units.
I mean I checked. They start off resting=false, then the unit moves, and it flips to resting=true which doesn't make sense :/

Edit:
The code with the last_x,last_y fix ca_wf_move_last.lua
vghetto
Posts: 755
Joined: November 2nd, 2019, 5:12 pm

Re: Order of AI moves

Post by vghetto »

hmm, force_gamestate_change didn't work. The ca still got blacklisted.

I added the following to the definition file micro_ai.lua. (adapted for 1.14)

Code: Select all

-- force_gamestate_change by mattsc
local utils = wesnoth.require "wml-utils"
function utils.force_gamestate_change(ai)
    -- Can be done using any unit of the AI side; works even if the unit already has 0 moves
    local unit = wesnoth.get_units { side = wesnoth.current.side }[1]
    local cfg_reset_moves = { id = unit.id, moves = unit.moves }
    ai.stopunit_moves(unit)
    std_print("force_gamestate_change on " .. cfg_reset_moves.id)
    wesnoth.invoke_synced_command('reset_moves', cfg_reset_moves)
end

-- reset_moves by mattsc
function wesnoth.custom_synced_commands.reset_moves(cfg)
    local unit = wesnoth.get_units { id = cfg.id }[1]
    unit.moves = cfg.moves
    std_print("synced command on " .. cfg.id)
end
and called utils.force_gamestate_change(ai) at the end of execution()
In the game inspector, it said enabled=no for the move last ca.

Edit:
Unfortunately, this part turned out not to be correct about "works even if the unit already has 0 moves"

Code: Select all

    -- Can be done using any unit of the AI side; works even if the unit already has 0 moves
    local unit = wesnoth.get_units { side = wesnoth.current.side }[1]
It needs the unit to have moves for it to work.
Replacing wesnoth.get_units with local unit = AH.get_units_with_moves { side = wesnoth.current.side }[1] gets it back to track and won't blacklist the CA anymore. (warning: It will go into an infinite loop in this example)
vghetto
Posts: 755
Joined: November 2nd, 2019, 5:12 pm

Re: Order of AI moves

Post by vghetto »

Ok, guys here it is.
move_last micro_ai with forced gamestate change and no blacklisting or infinite loop. This includes the resting hack (still no clue why that happens, maybe ai.stopunit_all sets it?)
Thanks to mattsc for the force gamestate trick.
Attachments
Bug_Tester.zip
(3.74 KiB) Downloaded 37 times
mattsc
Inactive Developer
Posts: 1217
Joined: October 13th, 2010, 6:14 pm

Re: Order of AI moves

Post by mattsc »

Glad you got it to work. As for the gamestate changed comment, looks like I actually changed (fixed) that myself about three years ago and then forgot about it. I copied the code I posted up there out of an existing AI that I apparently wrote before that. Sorry for that.

I don't know anything about the resting behavior, somebody else will have to comment on that.
Post Reply