Question: Detecting AI on Network Multiplayer
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.
Question: Detecting AI on Network Multiplayer
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.
Are there any to-go code for an AI detector? If you know one, please tell me!
Thank you.
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]
Thank you.
My MP Eras and Mods with core units and unusual gameplay (version 1.3.1 for Wesnoth 1.12)
Re: Question: Detecting AI on Network Multiplayer
you can read the [side]controller= value, for example like this:
the value of is_ai_controlled might be different on different clients, in order to prevent OOS you have to use wesnoth.syncronize_choice:
examples above are untested and might contain typos/bugs.
Code: Select all
local side_number = ...
local is_ai_controlled = wesnoth.sides[side_number].controller == "ai" or wesnoth.sides[side_number].controller == "network_ai"
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
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.
Re: Question: Detecting AI on Network Multiplayer
Thanks!
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.
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)
Re: Question: Detecting AI on Network Multiplayer
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.
Re: Question: Detecting AI on Network Multiplayer
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)
Re: Question: Detecting AI on Network Multiplayer
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
you could write:
again, this is untested code.
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)
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)
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.
Re: Question: Detecting AI on Network Multiplayer
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.
EDIT:
Variation taking in account new LuaAPI changes (thanks to @Lord-Knightmare )
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
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...
- Celtic_Minstrel
- Developer
- Posts: 2211
- Joined: August 3rd, 2012, 11:26 pm
- Location: Canada
- Contact:
Re: Question: Detecting AI on Network Multiplayer
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:
Even if you prefer to use an
To explain what's different here:
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]
[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]
- 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 afirst_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. - 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 areturn
statement that evaluates to true or false.
- You should replace
wesnoth.synchronize_choice
withwesnoth.sync.evaluate_single
. - It's not relevant if you follow suggestion 1, but
wesnoth.set_variable("xyz", value)
also becomeswesnoth.variables.xyz = value
orwesnoth.variables["xyz"] = value
. - Finally, instead of writing
value = "no"
orvalue = "yes"
in Lua, you should writevalue = false
orvalue = true
respectively (note the lack of quotes).
Re: Question: Detecting AI on Network Multiplayer
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:
Note: This works only if the primary unit is at $x1 $y1. This is always the case, except for certain events like
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.
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...
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.
- Celtic_Minstrel
- Developer
- Posts: 2211
- Joined: August 3rd, 2012, 11:26 pm
- Location: Canada
- Contact:
Re: Question: Detecting AI on Network Multiplayer
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
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
.