Question: Detecting AI on Network Multiplayer

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
User avatar
watbesh
Posts: 55
Joined: August 29th, 2012, 2:21 am
Location: Kanagawa, Japan

Question: Detecting AI on Network Multiplayer

Post by watbesh »

Hello.

I'm working on multiplayer modifications for 1.12, and I need to code an "AI detector" into the mod's code so I can use it for vs-AI games and survivals. The mods have to know whether the current side is controlled by an AI or a human player, to trigger events, for example, on recruiting a unit (if the side is AI-controlled, the AI always takes a certain option). I know two ways of doing this, but neither is likely to do well on online multiplayer. They seem to work when I play with AI locally.

Code: Select all

    [event]
        name=side turn
        first_time_only=no
        id=foo_sideturn
        {VARIABLE human_control[$side_number] yes}
    [/event]

    [event]
        name=ai turn
        first_time_only=no
        id=foo_aiturn
       {VARIABLE human_control[$side_number] no}
    [/event]

    # Then I can read the array "human_control" to get the controller. According to WML Reference, "ai turn" breaks replays or outright non-MP-safe.

Code: Select all

    [store_unit]
        [filter]
            canrecruit=yes
            side=$side_number
        [/filter]
        variable=side_leader
    [/store_unit]
    [if]
        [variable]
            name=side_leader.name
            not_equals={STR_RCA_AI} + " 1"
        [/variable]
        # {STR_RCA_AI} is a macro to call _"RCA AI" from "#textdomain wesnoth". Proceeds to other possible AI leader names.
        [then]
            # Event for a human player, but this is also triggered for humans who droid.
        [/then]
        [else]
            # Event for an AI, but this is also triggered for humans who are named "RCA AI".
        [/else]
    [/if]
Are there any to-go code for an AI detector? If you know one, please tell me!

Thank you.
My MP Eras and Mods with core units and unusual gameplay (version 1.3.1 for Wesnoth 1.12)
gfgtdf
Developer
Posts: 1431
Joined: February 10th, 2013, 2:25 pm

Re: Question: Detecting AI on Network Multiplayer

Post by gfgtdf »

you can read the [side]controller= value, for example like this:

Code: Select all

local side_number = ...
local is_ai_controlled = wesnoth.sides[side_number].controller == "ai" or  wesnoth.sides[side_number].controller == "network_ai"
the value of is_ai_controlled might be different on different clients, in order to prevent OOS you have to use wesnoth.syncronize_choice:

Code: Select all

local side_number = ...
local is_ai_controlled = wesnoth.synchronize_choice(
  function() 
    return { is_ai = wesnoth.sides[side_number].controller == "ai" or  wesnoth.sides[side_number].controller == "network_ai" }
  end
).is_ai
examples above are untested and might contain typos/bugs.
Scenario with Robots SP scenario (1.11/1.12), allows you to build your units with components, PYR No preperation turn 1.12 mp-mod that allows you to select your units immideately after the game begins.
User avatar
watbesh
Posts: 55
Joined: August 29th, 2012, 2:21 am
Location: Kanagawa, Japan

Re: Question: Detecting AI on Network Multiplayer

Post by watbesh »

Thanks! :D
But I haven't seen that format in my WML... I guess it's a Lua code.

I'll learn some about that sort of coding. Possibly I can learn something more than an AI-detector.
My MP Eras and Mods with core units and unusual gameplay (version 1.3.1 for Wesnoth 1.12)
gfgtdf
Developer
Posts: 1431
Joined: February 10th, 2013, 2:25 pm

Re: Question: Detecting AI on Network Multiplayer

Post by gfgtdf »

yes it's lua code, the first code would also be possible to write in wml, but not the second code because wesnoth.syncronize_choice is only available in lua.
Scenario with Robots SP scenario (1.11/1.12), allows you to build your units with components, PYR No preperation turn 1.12 mp-mod that allows you to select your units immideately after the game begins.
User avatar
watbesh
Posts: 55
Joined: August 29th, 2012, 2:21 am
Location: Kanagawa, Japan

Re: Question: Detecting AI on Network Multiplayer

Post by watbesh »

After a bunch of trial-and-error, I made a code that works locally. This is probably redundant, and possibly lacks something important, but I leave this here to help creators (if any) who want an MP-safe AI Detector... or to ask for further advice.

Code: Select all

#define CONTROL_ACTION HUMAN_ACTION_WML AI_ACTION_WML
    [lua]
        code = <<
          local current_side_number = wesnoth.get_variable("side_number")
          local side_control_human = wesnoth.synchronize_choice(
            function()
              return { is_human = wesnoth.sides[current_side_number].controller == "human" or wesnoth.sides[current_side_number].controller == "network" }
            end
          ).is_human
          local side_control_ai = wesnoth.synchronize_choice(
            function()
              return { is_ai = wesnoth.sides[current_side_number].controller == "ai" or wesnoth.sides[current_side_number].controller == "network_ai" }
            end
          ).is_ai
          wesnoth.set_variable("human_control", side_control_human)
          wesnoth.set_variable("ai_control", side_control_ai)
        >>
    [/lua]

    {IF_VAR human_control boolean_equals yes (
        [then]
            {HUMAN_ACTION_WML}
        [/then]
    )}

    {IF_VAR ai_control boolean_equals yes (
        [then]
            {AI_ACTION_WML}
        [/then]
    )}
#enddef
My MP Eras and Mods with core units and unusual gameplay (version 1.3.1 for Wesnoth 1.12)
gfgtdf
Developer
Posts: 1431
Joined: February 10th, 2013, 2:25 pm

Re: Question: Detecting AI on Network Multiplayer

Post by gfgtdf »

Yes that could work.
It's true that side not beeing human controlled doesn't imply a side beeing ai controlled becasue it might also be null-controlled. But turns of null-controlled sides are usually skipped, thats why you can assume that the currently playing side is never null-controlled. (unless you are in a start or a prestart event). So in this case one check would be enough and you could then put the the other case in the [else] block.


EDIT:
you can also put both checks in one sync_choice, that might make sense becasue sync choice implies netowrk traffic so you shouldn't use it if uneserary, that's why it can also make you wml slower, although it shouldnt matter it you use it one 2 times.
so instead of

Code: Select all

 
          local current_side_number = wesnoth.get_variable("side_number")
          local side_control_human = wesnoth.synchronize_choice(
            function()
              return { is_human = wesnoth.sides[current_side_number].controller == "human" or wesnoth.sides[current_side_number].controller == "network" }
            end
          ).is_human
          local side_control_ai = wesnoth.synchronize_choice(
            function()
              return { is_ai = wesnoth.sides[current_side_number].controller == "ai" or wesnoth.sides[current_side_number].controller == "network_ai" }
            end
          ).is_ai
          wesnoth.set_variable("human_control", side_control_human)
          wesnoth.set_variable("ai_control", side_control_ai)
you could write:

Code: Select all

 
          local choice = wesnoth.synchronize_choice(
            function()
              local side = wesnoth.sides[wesnoth.current.side]
              return {
                is_human = side.controller == "human" or side.controller == "network", 
                is_ai = side.controller == "ai" or side.controller == "network_ai",
              }
            end
          )
          wesnoth.set_variable("human_control", choice.is_human)
          wesnoth.set_variable("ai_control", choice.is_ai)
again, this is untested code.
Scenario with Robots SP scenario (1.11/1.12), allows you to build your units with components, PYR No preperation turn 1.12 mp-mod that allows you to select your units immideately after the game begins.
User avatar
Toranks
Translator
Posts: 168
Joined: October 21st, 2022, 8:59 pm
Location: Sevilla
Contact:

Re: Question: Detecting AI on Network Multiplayer

Post by Toranks »

I'm "resurrecting" this old thread to confirm that gfgtdf code still works on 1.16 and 1.17, and to leave another usage example here. In this case, different experience points are given each turn to humans and AI to all living units that are not still on recall list. Sides with "null" controller are ignored.

Code: Select all

#ifdef MULTIPLAYER
[event]
    name=side turn end
    id=aww_04_trigger_passive_xp_multi
    first_time_only=no
    [lua]
        code = <<
          local choice = wesnoth.synchronize_choice(
            function()
              local side = wesnoth.sides[wesnoth.current.side]
              return {
                is_human = side.controller == "human" or side.controller == "network", 
                is_ai = side.controller == "ai" or side.controller == "network_ai",
              }
            end
          )
          wesnoth.set_variable("human_control", choice.is_human)
          wesnoth.set_variable("ai_control", choice.is_ai)
        >>
    [/lua]
    [if]
		[variable]
			name=human_control
			equals=yes
		[/variable]
        {AWW_ENABLED_FEATURE_04}
        [then]
			[store_unit]
				variable=field_player
				[filter]
					[not]
						x="recall"
						y="recall"
					[/not]
					[filter_side]
						side=$side_number
					[/filter_side]
				[/filter]
			[/store_unit]
			{FOREACH field_player i}
				{VARIABLE_OP field_player[$i].experience add $aww_04_passive_xp}
				[unstore_unit]
					variable=field_player[$i]
				[/unstore_unit]
			{NEXT i}
			{CLEAR_VARIABLE field_player,human_control,ai_control}
        [/then]
		[elseif]
			[variable]
				name=ai_control
				equals=yes
			[/variable]
			{AWW_ENABLED_FEATURE_04AI}			
			[then]
				[store_unit]
					variable=field_enemy
					[filter]
						[filter_side]
							side=$side_number
						[/filter_side]
					[/filter]
				[/store_unit]
				{FOREACH field_enemy i}
					{VARIABLE_OP field_enemy[$i].experience add $aww_04_passive_xp_ai}
					[unstore_unit]
						variable=field_enemy[$i]
					[/unstore_unit]
				{NEXT i}
				{CLEAR_VARIABLE field_enemy,human_control,ai_control}
			[/then]		
		[/elseif]
	[/if]
[/event]			
#endif
EDIT:
Variation taking in account new LuaAPI changes (thanks to @Lord-Knightmare )

Code: Select all

[event]
    name=side turn end
    id=aww_04_trigger_passive_xp_multi
    first_time_only=no
    [lua]
        code = <<
			local result = wesnoth.synchronize_choice(
                function()
                    return { value = "no" }
                end,
                function()
                    -- Called only on the client handling the current side, if it is an AI.
                    return { value = "yes" }
                end)
            wesnoth.set_variable("aww_is_ai",result.value)
        >>
    [/lua]
    [if]
		[variable]
			name=aww_is_ai
			equals=no
		[/variable]
etc...
User avatar
Celtic_Minstrel
Developer
Posts: 2158
Joined: August 3rd, 2012, 11:26 pm
Location: Canada
Contact:

Re: Question: Detecting AI on Network Multiplayer

Post by Celtic_Minstrel »

Two things.

1. If that's an event that's only supposed to trigger when an AI side's turn ends, I would recommend the following structure instead:

Code: Select all

[event]
	name=side turn end
	id=whatever
	first_time_only=no
	[filter_condition]
		[lua]
			code=<<
				-- the synchronize code goes here
				-- instead of setting a variable, return like this:
				return result.value
			>>
		[/lua]
	[/filter_condition]
	# Actions go here, no need for an [if]
[/event]
Even if you prefer to use an [if], or if you require one because you need to do different actions on non-AI turns, I would recommend the following structure instead:

Code: Select all

[event]
	name=side turn end
	id=whatever
	first_time_only=no
	[if]
		[lua]
			code=<<...exact same as in my previous example...>>
		[/lua]
		[then]
			# Actions for an AI side
		[/then]
		[else]
			# Actions for a non-AI side
		[/else]
	[/if]
[/event]
To explain what's different here:
  1. Using a [filter_condition] instead of an [if] means the event never triggers at all if the condition doesn't pass. This doesn't make a huge difference in this example, but if you wanted a first_time_only=yes event it would be crucial to handle it this way – using [filter_condition] would mean it triggers at the end of the first AI turn, but using [if] would mean it triggers at the end of the first turn, which is probably not an AI, so it then does nothing and never triggers at the end of the first AI turn.
  2. Your examples use [lua] as an ActionWML tag, which is the most common use-case. It also has two other use-cases. One is the "global Lua tag", which usually means placing it outside of an event, such as in a [campaign] or [era]. The other use-case, which is demonstrated here, is using it as a ConditionalWML tag. This allows you to place it anywhere you'd put a [variable] or [have_unit] tag, but also means the code must end with a return statement that evaluates to true or false.
2. That Lua code is not updated to the latest LuaAPI.
  1. You should replace wesnoth.synchronize_choice with wesnoth.sync.evaluate_single.
  2. It's not relevant if you follow suggestion 1, but wesnoth.set_variable("xyz", value) also becomes wesnoth.variables.xyz = value or wesnoth.variables["xyz"] = value.
  3. Finally, instead of writing value = "no" or value = "yes" in Lua, you should write value = false or value = true respectively (note the lack of quotes).
Author of The Black Cross of Aleron campaign and Default++ era.
Former maintainer of Steelhive.
User avatar
Toranks
Translator
Posts: 168
Joined: October 21st, 2022, 8:59 pm
Location: Sevilla
Contact:

Re: Question: Detecting AI on Network Multiplayer

Post by Toranks »

Resurrecting this post again, as it has a very clear title, and it a appears easily in forum or Google searches when someone searches for "AI" and "Multiplayer".

If you need to detect if a particular unit is from the AI or the human player, instead of the side's turn. For example, on a post_advance event (which can happen during a different side's fight, so the above method doesn't work).

Thanks to @celtic_minstrel again. It works the same way, it will return the aww_is_ai variable with the value "no" if it is human and "yes" if it is AI, with which you can now freely play with conditionals within the event:

Code: Select all

[event]
    name=post advance,unit placed
    first_time_only=no
   [lua]
        code = <<
            local result = wesnoth.sync.evaluate_single(
                function()
                    return { value = "no" }
                end,
                function()
                    -- Called only on the client handling the current side, if it is an AI.
                    return { value = "yes" }
                end,
                wesnoth.units.get(wesnoth.current.event_context.unit_x, wesnoth.current.event_context.unit_y).side
                )
            wml.variables["aww_is_ai"] = result.value
        >>
    [/lua]
    [if]
	{VARIABLE_CONDITIONAL aww_is_ai equals no}
	[then]
        etc...
Note: This works only if the primary unit is at $x1 $y1. This is always the case, except for certain events like exit_hex

Alternative using [lua] as ConditionalWML as celticminstrel explained before. Note that YOU CAN'T use this [lua] as ConditionalWML inside [filter_condition], because wesnoth.current.event_context need the event be started to receive the unit data.

Code: Select all

[event]
    name=post advance,unit placed
    first_time_only=no
    [if]
      [lua]
          code = <<
            local result = wesnoth.sync.evaluate_single(
                function()
                    return { value = "no" }
                end,
                function()
                    -- Called only on the client handling the current side, if it is an AI.
                    return { value = "yes" }
                end,
                wesnoth.units.get(wesnoth.current.event_context.unit_x, wesnoth.current.event_context.unit_y).side
                )
            return result.value
          >>
       [/lua]
       [then]
etc.
Last edited by Toranks on February 10th, 2023, 7:18 pm, edited 2 times in total.
User avatar
Celtic_Minstrel
Developer
Posts: 2158
Joined: August 3rd, 2012, 11:26 pm
Location: Canada
Contact:

Re: Question: Detecting AI on Network Multiplayer

Post by Celtic_Minstrel »

I'd like to add a few minor improvements to that code.

Code: Select all

local ec = wesnoth.current.event_context
local result = wesnoth.sync.evaluate_single(
	function()
		return { value = false }
	end,
	function()
		-- Called only on the client handling the current side, if it is an AI.
		return { value = true }
	end,
	wesnoth.units.get(ec.unit_x, ec.unit_y).side
)
wml.variables["aww_is_ai"] = result.value
Toranks wrote: February 10th, 2023, 6:51 pm Note: This works only if the primary unit is at $x1 $y1. This is always the case, except for certain events like exit_hex
I've already accounted for that discrepancy by using unit_x, unit_y instead of x1, y1. So, theoretically it should work even in exit_hex.
Author of The Black Cross of Aleron campaign and Default++ era.
Former maintainer of Steelhive.
Post Reply