From 65ee9be0cc1b387aa75320d1b80eee5fb64f8293 Mon Sep 17 00:00:00 2001 From: tvillegas98 Date: Tue, 21 Jan 2025 18:23:24 -0300 Subject: [PATCH 01/14] feat: random position in a circle --- apps/bot_manager/lib/utils.ex | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/apps/bot_manager/lib/utils.ex b/apps/bot_manager/lib/utils.ex index 5e6427fe0..489dd3448 100644 --- a/apps/bot_manager/lib/utils.ex +++ b/apps/bot_manager/lib/utils.ex @@ -6,4 +6,11 @@ defmodule BotManager.Utils do def player_alive?(%{aditional_info: {:player, %{health: health}}}), do: health > 0 def player_alive?(_), do: :not_a_player + + def random_position_within_safe_zone_radius(safe_zone_radius) do + x = Enum.random(-safe_zone_radius..safe_zone_radius) / 1.0 + y = Enum.random(-safe_zone_radius..safe_zone_radius) / 1.0 + + %{x: x, y: y} + end end From a471c4f94f41a11a62fb6b2f0735b6097dc48ea7 Mon Sep 17 00:00:00 2001 From: tvillegas98 Date: Tue, 21 Jan 2025 18:24:14 -0300 Subject: [PATCH 02/14] feat: make the bot move between positions and not in random directions --- apps/bot_manager/lib/bot_state_machine.ex | 56 +++++++++---------- .../lib/bot_state_machine_checker.ex | 19 ++++++- 2 files changed, 44 insertions(+), 31 deletions(-) diff --git a/apps/bot_manager/lib/bot_state_machine.ex b/apps/bot_manager/lib/bot_state_machine.ex index 421ff1736..3bfa097da 100644 --- a/apps/bot_manager/lib/bot_state_machine.ex +++ b/apps/bot_manager/lib/bot_state_machine.ex @@ -48,7 +48,7 @@ defmodule BotManager.BotStateMachine do case next_state do :moving -> - move(bot_player, bot_state_machine) + move(bot_player, bot_state_machine, game_state.zone.radius) :attacking -> use_skill(%{ @@ -81,7 +81,7 @@ defmodule BotManager.BotStateMachine do players_with_distances = map_directions_to_players(game_state, bot_player, @vision_range) if Enum.empty?(players_with_distances) do - move(bot_player, bot_state_machine) + move(bot_player, bot_state_machine, game_state.zone.radius) else cond do bot_state_machine.progress_for_ultimate_skill >= bot_state_machine.cap_for_ultimate_skill -> @@ -112,7 +112,7 @@ defmodule BotManager.BotStateMachine do %{action: {:use_skill, @skill_1_key, direction}, bot_state_machine: bot_state_machine} true -> - move(bot_player, bot_state_machine) + move(bot_player, bot_state_machine, game_state.zone.radius) end end end @@ -169,13 +169,6 @@ defmodule BotManager.BotStateMachine do defp determine_player_move_action(bot_player, bot_state_machine, direction) do {:player, bot_player_info} = bot_player.aditional_info - direction = - if BotStateMachineChecker.should_bot_rotate_its_direction?(bot_state_machine) do - Vector.rotate_by_degrees(direction, :rand.uniform() * 360) - else - direction - end - if Map.has_key?(bot_player_info.cooldowns, @dash_skill_key) do {:move, direction} else @@ -183,26 +176,11 @@ defmodule BotManager.BotStateMachine do end end - # This function will change the bot’s direction by checking if it has stayed in the same position between the last and current update. - # If the bot hasn’t moved, it will randomly switch its direction. - defp maybe_switch_direction(bot_player, bot_state_machine) do - x_distance = abs(bot_state_machine.current_position.x - bot_state_machine.previous_position.x) - y_distance = abs(bot_state_machine.current_position.y - bot_state_machine.previous_position.y) - - if x_distance < @min_distance_to_switch and y_distance < @min_distance_to_switch, - do: switch_direction_randomly(bot_player.direction), - else: bot_player.direction - end - - defp switch_direction_randomly(direction) do - Vector.rotate_by_degrees(direction, Enum.random([90, 180, 270])) - end - defp run_away(bot_player, game_state, bot_state_machine) do players_with_distances = map_directions_to_players(game_state, bot_player, @vision_range) if Enum.empty?(players_with_distances) do - move(bot_player, bot_state_machine) + move(bot_player, bot_state_machine, game_state.zone.radius) else closest_player = Enum.min_by(players_with_distances, & &1.distance) @@ -225,12 +203,34 @@ defmodule BotManager.BotStateMachine do end end - defp move(bot_player, bot_state_machine) do - direction = maybe_switch_direction(bot_player, bot_state_machine) + defp move(bot_player, bot_state_machine, safe_zone_radius) do + bot_state_machine = determine_position_to_move_to(bot_state_machine, safe_zone_radius) + + %{direction: direction} = + get_distance_and_direction_to_positions(bot_state_machine.current_position, bot_state_machine.position_to_move_to) %{ action: determine_player_move_action(bot_player, bot_state_machine, direction), bot_state_machine: bot_state_machine } end + + defp determine_position_to_move_to(bot_state_machine, safe_zone_radius) do + cond do + is_nil(bot_state_machine.position_to_move_to) -> + position_to_move_to = BotManager.Utils.random_position_within_safe_zone_radius(floor(safe_zone_radius)) + + Map.put(bot_state_machine, :position_to_move_to, position_to_move_to) + |> Map.put(:last_time_position_changed, :os.system_time(:millisecond)) + + BotStateMachineChecker.should_bot_move_to_another_position?(bot_state_machine) -> + position_to_move_to = BotManager.Utils.random_position_within_safe_zone_radius(floor(safe_zone_radius)) + + Map.put(bot_state_machine, :position_to_move_to, position_to_move_to) + |> Map.put(:last_time_position_changed, :os.system_time(:millisecond)) + + true -> + bot_state_machine + end + end end diff --git a/apps/bot_manager/lib/bot_state_machine_checker.ex b/apps/bot_manager/lib/bot_state_machine_checker.ex index e71a2e838..3ad5be247 100644 --- a/apps/bot_manager/lib/bot_state_machine_checker.ex +++ b/apps/bot_manager/lib/bot_state_machine_checker.ex @@ -22,8 +22,12 @@ defmodule BotManager.BotStateMachineChecker do :time_to_change_direction, # The last time that the bot changed its direction :last_time_direction_changed, - # The time that the bot has been moving in the same direction - :current_time_in_direction + # The position that the bot is going to move to + :position_to_move_to, + # The time that the bot is going to take to change its position in milliseconds + :time_amount_to_change_position, + # The last time that the bot changed its position + :last_time_position_changed ] def new do @@ -37,7 +41,9 @@ defmodule BotManager.BotStateMachineChecker do current_position: nil, time_to_change_direction: 1600, last_time_direction_changed: 0, - current_time_in_direction: 0 + position_to_move_to: nil, + time_amount_to_change_position: 2000, + last_time_position_changed: 0 } end @@ -67,4 +73,11 @@ defmodule BotManager.BotStateMachineChecker do time_since_last_direction_change >= bot_state_machine.time_to_change_direction end + + def should_bot_move_to_another_position?(bot_state_machine) do + current_time = :os.system_time(:millisecond) + time_since_last_position_change = current_time - bot_state_machine.last_time_position_changed + + time_since_last_position_change >= bot_state_machine.time_amount_to_change_position + end end From 2f4866ea8dd4cd56a1e5bef93c7ba478771a3d77 Mon Sep 17 00:00:00 2001 From: tvillegas98 Date: Tue, 21 Jan 2025 18:53:40 -0300 Subject: [PATCH 03/14] chore: remove no longer used logic --- apps/bot_manager/lib/bot_state_machine_checker.ex | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/apps/bot_manager/lib/bot_state_machine_checker.ex b/apps/bot_manager/lib/bot_state_machine_checker.ex index 3ad5be247..bd5654ec7 100644 --- a/apps/bot_manager/lib/bot_state_machine_checker.ex +++ b/apps/bot_manager/lib/bot_state_machine_checker.ex @@ -18,10 +18,6 @@ defmodule BotManager.BotStateMachineChecker do :cap_for_basic_skill, # This is the maximum value that the progress_for_ultimate_skill can reach :cap_for_ultimate_skill, - # The time that the bot is going to take to change its direction in milliseconds - :time_to_change_direction, - # The last time that the bot changed its direction - :last_time_direction_changed, # The position that the bot is going to move to :position_to_move_to, # The time that the bot is going to take to change its position in milliseconds @@ -39,8 +35,6 @@ defmodule BotManager.BotStateMachineChecker do cap_for_ultimate_skill: 3, previous_position: nil, current_position: nil, - time_to_change_direction: 1600, - last_time_direction_changed: 0, position_to_move_to: nil, time_amount_to_change_position: 2000, last_time_position_changed: 0 @@ -67,13 +61,6 @@ defmodule BotManager.BotStateMachineChecker do bot_state_machine.progress_for_ultimate_skill >= bot_state_machine.cap_for_ultimate_skill end - def should_bot_rotate_its_direction?(bot_state_machine) do - current_time = :os.system_time(:millisecond) - time_since_last_direction_change = current_time - bot_state_machine.last_time_direction_changed - - time_since_last_direction_change >= bot_state_machine.time_to_change_direction - end - def should_bot_move_to_another_position?(bot_state_machine) do current_time = :os.system_time(:millisecond) time_since_last_position_change = current_time - bot_state_machine.last_time_position_changed From dd4b40c86a41a86d20fa017c94f4630b030fbc2d Mon Sep 17 00:00:00 2001 From: tvillegas98 Date: Fri, 24 Jan 2025 16:23:14 -0300 Subject: [PATCH 04/14] refactor: move some functions to the utils module --- apps/bot_manager/lib/utils.ex | 50 +++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/apps/bot_manager/lib/utils.ex b/apps/bot_manager/lib/utils.ex index 489dd3448..3add9b98d 100644 --- a/apps/bot_manager/lib/utils.ex +++ b/apps/bot_manager/lib/utils.ex @@ -3,6 +3,8 @@ defmodule BotManager.Utils do utils to work with nested game state operations """ + alias BotManager.Math.Vector + def player_alive?(%{aditional_info: {:player, %{health: health}}}), do: health > 0 def player_alive?(_), do: :not_a_player @@ -13,4 +15,52 @@ defmodule BotManager.Utils do %{x: x, y: y} end + + # This function will map the directions and distance from the bot to the players. + def map_directions_to_players(players, bot_player, max_distance) do + Map.delete(players, bot_player.id) + |> Map.filter(fn {player_id, player} -> + player_alive?(player) && player_within_visible_players?(bot_player, player_id) && + not bot_belongs_to_the_same_team?(bot_player, player) + end) + |> Enum.map(fn {_player_id, player} -> + player_info = + get_distance_and_direction_to_positions(bot_player.position, player.position) + + Map.merge(player, player_info) + end) + |> Enum.filter(fn player_info -> player_info.distance <= max_distance end) + end + + def get_distance_and_direction_to_positions(base_position, base_position) do + %{ + direction: %{x: 0, y: 0}, + distance: 0 + } + end + + def get_distance_and_direction_to_positions(base_position, end_position) do + %{x: x, y: y} = Vector.sub(end_position, base_position) + + distance = :math.sqrt(:math.pow(x, 2) + :math.pow(y, 2)) + + direction = %{x: x / distance, y: y / distance} + + %{ + direction: direction, + distance: distance + } + end + + defp player_within_visible_players?(bot_player, player_id) do + {:player, bot_player_info} = bot_player.aditional_info + Enum.member?(bot_player_info.visible_players, player_id) + end + + defp bot_belongs_to_the_same_team?(bot_player, player) do + {:player, bot_player_info} = bot_player.aditional_info + {:player, player_info} = player.aditional_info + + bot_player_info.team == player_info.team + end end From 46ef8e4cffce469a4d8662f94b0e8c20c95c1057 Mon Sep 17 00:00:00 2001 From: tvillegas98 Date: Fri, 24 Jan 2025 16:23:26 -0300 Subject: [PATCH 05/14] feat: make bots track nearby players --- apps/bot_manager/lib/bot_state_machine.ex | 87 +++++++------------ .../lib/bot_state_machine_checker.ex | 27 +++++- 2 files changed, 55 insertions(+), 59 deletions(-) diff --git a/apps/bot_manager/lib/bot_state_machine.ex b/apps/bot_manager/lib/bot_state_machine.ex index 3bfa097da..1263f6d54 100644 --- a/apps/bot_manager/lib/bot_state_machine.ex +++ b/apps/bot_manager/lib/bot_state_machine.ex @@ -11,8 +11,6 @@ defmodule BotManager.BotStateMachine do @skill_1_key "1" @skill_2_key "2" @dash_skill_key "3" - @vision_range 1200 - @min_distance_to_switch 10 def decide_action(%{bots_enabled?: false, bot_state_machine: bot_state_machine}) do %{action: {:move, %{x: 0, y: 0}}, bot_state_machine: bot_state_machine} @@ -39,12 +37,15 @@ defmodule BotManager.BotStateMachine do end %{distance: distance} = - get_distance_and_direction_to_positions(bot_state_machine.previous_position, bot_state_machine.current_position) + Utils.get_distance_and_direction_to_positions( + bot_state_machine.previous_position, + bot_state_machine.current_position + ) bot_state_machine = Map.put(bot_state_machine, :progress_for_basic_skill, bot_state_machine.progress_for_basic_skill + distance) - next_state = BotStateMachineChecker.move_to_next_state(bot_player, bot_state_machine) + next_state = BotStateMachineChecker.move_to_next_state(bot_player, bot_state_machine, game_state.players) case next_state do :moving -> @@ -60,6 +61,25 @@ defmodule BotManager.BotStateMachine do :running_away -> run_away(bot_player, game_state, bot_state_machine) + + :tracking_player -> + players_with_distances = + Utils.map_directions_to_players( + game_state.players, + bot_player, + bot_state_machine.max_vision_range_to_follow_player + ) + + if Enum.empty?(players_with_distances) do + move(bot_player, bot_state_machine, game_state.zone.radius) + else + closest_player = Enum.min_by(players_with_distances, & &1.distance) + + %{ + action: determine_player_move_action(bot_player, bot_state_machine, closest_player.direction), + bot_state_machine: bot_state_machine + } + end end end @@ -78,7 +98,8 @@ defmodule BotManager.BotStateMachine do bot_player: bot_player, bot_state_machine: bot_state_machine }) do - players_with_distances = map_directions_to_players(game_state, bot_player, @vision_range) + players_with_distances = + Utils.map_directions_to_players(game_state.players, bot_player, bot_state_machine.vision_range_to_attack_player) if Enum.empty?(players_with_distances) do move(bot_player, bot_state_machine, game_state.zone.radius) @@ -117,54 +138,6 @@ defmodule BotManager.BotStateMachine do end end - # This function will map the directions and distance from the bot to the players. - defp map_directions_to_players(game_state, bot_player, max_distance) do - Map.delete(game_state.players, bot_player.id) - |> Map.filter(fn {player_id, player} -> - Utils.player_alive?(player) && player_within_visible_players?(bot_player, player_id) && - not bot_belongs_to_the_same_team?(bot_player, player) - end) - |> Enum.map(fn {_player_id, player} -> - player_info = - get_distance_and_direction_to_positions(bot_player.position, player.position) - - Map.merge(player, player_info) - end) - |> Enum.filter(fn player_info -> player_info.distance <= max_distance end) - end - - defp get_distance_and_direction_to_positions(base_position, base_position) do - %{ - direction: %{x: 0, y: 0}, - distance: 0 - } - end - - defp get_distance_and_direction_to_positions(base_position, end_position) do - %{x: x, y: y} = Vector.sub(end_position, base_position) - - distance = :math.sqrt(:math.pow(x, 2) + :math.pow(y, 2)) - - direction = %{x: x / distance, y: y / distance} - - %{ - direction: direction, - distance: distance - } - end - - defp player_within_visible_players?(bot_player, player_id) do - {:player, bot_player_info} = bot_player.aditional_info - Enum.member?(bot_player_info.visible_players, player_id) - end - - defp bot_belongs_to_the_same_team?(bot_player, player) do - {:player, bot_player_info} = bot_player.aditional_info - {:player, player_info} = player.aditional_info - - bot_player_info.team == player_info.team - end - # This function will determine the direction and action the bot will take. defp determine_player_move_action(bot_player, bot_state_machine, direction) do {:player, bot_player_info} = bot_player.aditional_info @@ -177,7 +150,8 @@ defmodule BotManager.BotStateMachine do end defp run_away(bot_player, game_state, bot_state_machine) do - players_with_distances = map_directions_to_players(game_state, bot_player, @vision_range) + players_with_distances = + Utils.map_directions_to_players(game_state, bot_player, bot_state_machine.vision_range_to_attack_player) if Enum.empty?(players_with_distances) do move(bot_player, bot_state_machine, game_state.zone.radius) @@ -207,7 +181,10 @@ defmodule BotManager.BotStateMachine do bot_state_machine = determine_position_to_move_to(bot_state_machine, safe_zone_radius) %{direction: direction} = - get_distance_and_direction_to_positions(bot_state_machine.current_position, bot_state_machine.position_to_move_to) + Utils.get_distance_and_direction_to_positions( + bot_state_machine.current_position, + bot_state_machine.position_to_move_to + ) %{ action: determine_player_move_action(bot_player, bot_state_machine, direction), diff --git a/apps/bot_manager/lib/bot_state_machine_checker.ex b/apps/bot_manager/lib/bot_state_machine_checker.ex index bd5654ec7..f26f26a87 100644 --- a/apps/bot_manager/lib/bot_state_machine_checker.ex +++ b/apps/bot_manager/lib/bot_state_machine_checker.ex @@ -2,9 +2,10 @@ defmodule BotManager.BotStateMachineChecker do @moduledoc """ This module will take care of deciding what the bot will do on each deciding step """ + alias BotManager.Utils @low_health_percentage 40 defstruct [ - # The bot state, these are the possible states: [:idling, :moving, :attacking, :running_away] + # The bot state, these are the possible states: [:idling, :moving, :attacking, :running_away, :tracking_player] :state, # The previous position of the bot :previous_position, @@ -23,7 +24,11 @@ defmodule BotManager.BotStateMachineChecker do # The time that the bot is going to take to change its position in milliseconds :time_amount_to_change_position, # The last time that the bot changed its position - :last_time_position_changed + :last_time_position_changed, + # The maximum vision range that the bot can follow a player + :max_vision_range_to_follow_player, + # The vision range that the bot has to find a player to attack + :vision_range_to_attack_player ] def new do @@ -37,13 +42,16 @@ defmodule BotManager.BotStateMachineChecker do current_position: nil, position_to_move_to: nil, time_amount_to_change_position: 2000, - last_time_position_changed: 0 + last_time_position_changed: 0, + max_vision_range_to_follow_player: 1500, + vision_range_to_attack_player: 1200 } end - def move_to_next_state(bot_player, bot_state_machine) do + def move_to_next_state(bot_player, bot_state_machine, players) do cond do bot_health_low?(bot_player) -> :running_away + bot_can_follow_a_player?(bot_player, bot_state_machine, players) -> :tracking_player bot_can_turn_aggresive?(bot_state_machine) -> :attacking true -> :moving end @@ -67,4 +75,15 @@ defmodule BotManager.BotStateMachineChecker do time_since_last_position_change >= bot_state_machine.time_amount_to_change_position end + + def bot_can_follow_a_player?(bot_player, bot_state_machine, players) do + players_nearby_to_follow = + Utils.map_directions_to_players(players, bot_player, bot_state_machine.max_vision_range_to_follow_player) + + players_nearby_to_attack = + Utils.map_directions_to_players(players, bot_player, bot_state_machine.vision_range_to_attack_player) + + Enum.empty?(players_nearby_to_attack) && not Enum.empty?(players_nearby_to_follow) && + bot_can_turn_aggresive?(bot_state_machine) + end end From 9ab7f550047eb85f5bcb8a655bd0b75e7f76353e Mon Sep 17 00:00:00 2001 From: tvillegas98 Date: Fri, 24 Jan 2025 16:48:04 -0300 Subject: [PATCH 06/14] docs: update bots documentation --- apps/arena/docs/src/bots/bots.md | 8 ++++++++ .../docs/src/bots/bots_aggresive_areas.png | Bin 0 -> 56798 bytes 2 files changed, 8 insertions(+) create mode 100644 apps/arena/docs/src/bots/bots_aggresive_areas.png diff --git a/apps/arena/docs/src/bots/bots.md b/apps/arena/docs/src/bots/bots.md index ee1e4a5c7..f0b17f1d2 100644 --- a/apps/arena/docs/src/bots/bots.md +++ b/apps/arena/docs/src/bots/bots.md @@ -36,6 +36,14 @@ Bots will enter into the attacking mode if they have charged enough energy to us This is just a gimmick added to prevent bots from using their shooting skills randomly and without purpose. When attacking, they will focus on the nearest player and won't consider health percentages or other factors, at least for now. +#### Tracking A Player + +This state arises when the bot becomes bloodthirsty, and there are no nearby enemies that can be easily hit. The bot is more likely to start following enemies in order to catch and attack them! + +Formally, for a bot to reach this state, players need to be near it but not close enough to be attacked. + +![aggresive areas](bots_aggresive_areas.png) + #### Running Away Bots will transition to this state whenever their health drops below a certain percentage. For now, this threshold is set at 40%. In this state, bots will attempt to escape from players by running in the opposite direction of the closest one. This does not necessarily mean they will run away from the player attacking them. diff --git a/apps/arena/docs/src/bots/bots_aggresive_areas.png b/apps/arena/docs/src/bots/bots_aggresive_areas.png new file mode 100644 index 0000000000000000000000000000000000000000..8da83110330fdca2462613132d87148f15d30ae2 GIT binary patch literal 56798 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 Date: Fri, 24 Jan 2025 17:10:32 -0300 Subject: [PATCH 07/14] chore: warnings --- apps/bot_manager/lib/bot_state_machine.ex | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/bot_manager/lib/bot_state_machine.ex b/apps/bot_manager/lib/bot_state_machine.ex index 1263f6d54..b265449ad 100644 --- a/apps/bot_manager/lib/bot_state_machine.ex +++ b/apps/bot_manager/lib/bot_state_machine.ex @@ -76,7 +76,7 @@ defmodule BotManager.BotStateMachine do closest_player = Enum.min_by(players_with_distances, & &1.distance) %{ - action: determine_player_move_action(bot_player, bot_state_machine, closest_player.direction), + action: determine_player_move_action(bot_player, closest_player.direction), bot_state_machine: bot_state_machine } end @@ -139,7 +139,7 @@ defmodule BotManager.BotStateMachine do end # This function will determine the direction and action the bot will take. - defp determine_player_move_action(bot_player, bot_state_machine, direction) do + defp determine_player_move_action(bot_player, direction) do {:player, bot_player_info} = bot_player.aditional_info if Map.has_key?(bot_player_info.cooldowns, @dash_skill_key) do @@ -162,7 +162,7 @@ defmodule BotManager.BotStateMachine do closest_player.direction |> Vector.normalize() |> Vector.rotate_by_degrees(180) %{ - action: determine_player_move_action(bot_player, bot_state_machine, direction), + action: determine_player_move_action(bot_player, direction), bot_state_machine: bot_state_machine } end @@ -187,7 +187,7 @@ defmodule BotManager.BotStateMachine do ) %{ - action: determine_player_move_action(bot_player, bot_state_machine, direction), + action: determine_player_move_action(bot_player, direction), bot_state_machine: bot_state_machine } end From 1d976d6748be67554e81b2c63643624270ee7eb3 Mon Sep 17 00:00:00 2001 From: tvillegas98 Date: Mon, 27 Jan 2025 12:05:39 -0300 Subject: [PATCH 08/14] fix: make the bot dash in the direction they're walking to --- apps/bot_manager/lib/bot_state_machine.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/bot_manager/lib/bot_state_machine.ex b/apps/bot_manager/lib/bot_state_machine.ex index b265449ad..caf2a8189 100644 --- a/apps/bot_manager/lib/bot_state_machine.ex +++ b/apps/bot_manager/lib/bot_state_machine.ex @@ -145,7 +145,7 @@ defmodule BotManager.BotStateMachine do if Map.has_key?(bot_player_info.cooldowns, @dash_skill_key) do {:move, direction} else - {:use_skill, @dash_skill_key, direction} + {:use_skill, @dash_skill_key, bot_player.direction} end end From f3adc4ff48d789a33f3be2c25483550cef449a28 Mon Sep 17 00:00:00 2001 From: tvillegas98 Date: Mon, 27 Jan 2025 14:58:48 -0300 Subject: [PATCH 09/14] feat: add distance function to vector module --- apps/bot_manager/lib/math/vector.ex | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/apps/bot_manager/lib/math/vector.ex b/apps/bot_manager/lib/math/vector.ex index 9102c8db2..7019c5d75 100644 --- a/apps/bot_manager/lib/math/vector.ex +++ b/apps/bot_manager/lib/math/vector.ex @@ -55,4 +55,8 @@ defmodule BotManager.Math.Vector do def deg2rad(deg) do deg * :math.pi() / 180 end + + def distance(%{x: x1, y: y1}, %{x: x2, y: y2}) do + :math.sqrt(:math.pow(x2 - x1, 2) + :math.pow(y2 - y1, 2)) + end end From e222418fea02b7056aa2ad04e2497f7f03898da3 Mon Sep 17 00:00:00 2001 From: tvillegas98 Date: Mon, 27 Jan 2025 15:01:07 -0300 Subject: [PATCH 10/14] feat: add new repositioning state --- apps/arena/docs/src/bots/bots.md | 4 ++ apps/bot_manager/lib/bot_state_machine.ex | 19 ++++++++ .../lib/bot_state_machine_checker.ex | 45 +++++++++++++------ 3 files changed, 54 insertions(+), 14 deletions(-) diff --git a/apps/arena/docs/src/bots/bots.md b/apps/arena/docs/src/bots/bots.md index f0b17f1d2..dd212a4ed 100644 --- a/apps/arena/docs/src/bots/bots.md +++ b/apps/arena/docs/src/bots/bots.md @@ -44,6 +44,10 @@ Formally, for a bot to reach this state, players need to be near it but not clos ![aggresive areas](bots_aggresive_areas.png) +#### Repositioning + +Bots will enter this state if they are stuck in a position where they cannot move, causing them to change their walking destination. + #### Running Away Bots will transition to this state whenever their health drops below a certain percentage. For now, this threshold is set at 40%. In this state, bots will attempt to escape from players by running in the opposite direction of the closest one. This does not necessarily mean they will run away from the player attacking them. diff --git a/apps/bot_manager/lib/bot_state_machine.ex b/apps/bot_manager/lib/bot_state_machine.ex index caf2a8189..bf1a42360 100644 --- a/apps/bot_manager/lib/bot_state_machine.ex +++ b/apps/bot_manager/lib/bot_state_machine.ex @@ -45,9 +45,28 @@ defmodule BotManager.BotStateMachine do bot_state_machine = Map.put(bot_state_machine, :progress_for_basic_skill, bot_state_machine.progress_for_basic_skill + distance) + bot_state_machine = + cond do + Vector.distance(bot_state_machine.previous_position, bot_state_machine.current_position) < 100 && + is_nil(bot_state_machine.start_time_stuck_in_position) -> + Map.put(bot_state_machine, :start_time_stuck_in_position, :os.system_time(:millisecond)) + |> Map.put(:stuck_in_position, bot_state_machine.current_position) + + not is_nil(bot_state_machine.stuck_in_position) && + Vector.distance(bot_state_machine.stuck_in_position, bot_state_machine.current_position) > 100 -> + Map.put(bot_state_machine, :start_time_stuck_in_position, nil) + |> Map.put(:stuck_in_position, nil) + + true -> + bot_state_machine + end + next_state = BotStateMachineChecker.move_to_next_state(bot_player, bot_state_machine, game_state.players) case next_state do + :repositioning -> + move(bot_player, bot_state_machine, game_state.zone.radius) + :moving -> move(bot_player, bot_state_machine, game_state.zone.radius) diff --git a/apps/bot_manager/lib/bot_state_machine_checker.ex b/apps/bot_manager/lib/bot_state_machine_checker.ex index f26f26a87..91aefa677 100644 --- a/apps/bot_manager/lib/bot_state_machine_checker.ex +++ b/apps/bot_manager/lib/bot_state_machine_checker.ex @@ -3,9 +3,12 @@ defmodule BotManager.BotStateMachineChecker do This module will take care of deciding what the bot will do on each deciding step """ alias BotManager.Utils + @low_health_percentage 40 + @time_stuck_in_position 400 + defstruct [ - # The bot state, these are the possible states: [:idling, :moving, :attacking, :running_away, :tracking_player] + # The bot state, these are the possible states: [:idling, :moving, :attacking, :running_away, :tracking_player, :repositioning] :state, # The previous position of the bot :previous_position, @@ -28,7 +31,11 @@ defmodule BotManager.BotStateMachineChecker do # The maximum vision range that the bot can follow a player :max_vision_range_to_follow_player, # The vision range that the bot has to find a player to attack - :vision_range_to_attack_player + :vision_range_to_attack_player, + # Start Time in the same position + :start_time_stuck_in_position, + # The position that the bot is stuck in + :stuck_in_position ] def new do @@ -44,12 +51,15 @@ defmodule BotManager.BotStateMachineChecker do time_amount_to_change_position: 2000, last_time_position_changed: 0, max_vision_range_to_follow_player: 1500, - vision_range_to_attack_player: 1200 + vision_range_to_attack_player: 1200, + stuck_in_position: nil, + start_time_stuck_in_position: nil } end def move_to_next_state(bot_player, bot_state_machine, players) do cond do + bot_stuck?(bot_state_machine) -> :repositioning bot_health_low?(bot_player) -> :running_away bot_can_follow_a_player?(bot_player, bot_state_machine, players) -> :tracking_player bot_can_turn_aggresive?(bot_state_machine) -> :attacking @@ -57,26 +67,26 @@ defmodule BotManager.BotStateMachineChecker do end end - def bot_health_low?(bot_player) do + def should_bot_move_to_another_position?(bot_state_machine) do + current_time = :os.system_time(:millisecond) + time_since_last_position_change = current_time - bot_state_machine.last_time_position_changed + + time_since_last_position_change >= bot_state_machine.time_amount_to_change_position + end + + defp bot_health_low?(bot_player) do {:player, bot_player_info} = bot_player.aditional_info health_percentage = bot_player_info.health * 100 / bot_player_info.max_health health_percentage <= @low_health_percentage end - def bot_can_turn_aggresive?(bot_state_machine) do + defp bot_can_turn_aggresive?(bot_state_machine) do bot_state_machine.progress_for_basic_skill >= bot_state_machine.cap_for_basic_skill || bot_state_machine.progress_for_ultimate_skill >= bot_state_machine.cap_for_ultimate_skill end - def should_bot_move_to_another_position?(bot_state_machine) do - current_time = :os.system_time(:millisecond) - time_since_last_position_change = current_time - bot_state_machine.last_time_position_changed - - time_since_last_position_change >= bot_state_machine.time_amount_to_change_position - end - - def bot_can_follow_a_player?(bot_player, bot_state_machine, players) do + defp bot_can_follow_a_player?(bot_player, bot_state_machine, players) do players_nearby_to_follow = Utils.map_directions_to_players(players, bot_player, bot_state_machine.max_vision_range_to_follow_player) @@ -84,6 +94,13 @@ defmodule BotManager.BotStateMachineChecker do Utils.map_directions_to_players(players, bot_player, bot_state_machine.vision_range_to_attack_player) Enum.empty?(players_nearby_to_attack) && not Enum.empty?(players_nearby_to_follow) && - bot_can_turn_aggresive?(bot_state_machine) + bot_can_turn_aggresive?(bot_state_machine) && not bot_stuck?(bot_state_machine) + end + + defp bot_stuck?(%{start_time_stuck_in_position: nil}), do: false + + defp bot_stuck?(bot_state_machine) do + time_stuck = :os.system_time(:millisecond) - bot_state_machine.start_time_stuck_in_position + time_stuck >= @time_stuck_in_position end end From 510778e21733a547b585c9eb0179398d440e1aa7 Mon Sep 17 00:00:00 2001 From: tvillegas98 Date: Mon, 27 Jan 2025 15:03:38 -0300 Subject: [PATCH 11/14] chore: repositioning was not needed --- apps/arena/docs/src/bots/bots.md | 4 ---- apps/bot_manager/lib/bot_state_machine.ex | 3 --- apps/bot_manager/lib/bot_state_machine_checker.ex | 2 +- 3 files changed, 1 insertion(+), 8 deletions(-) diff --git a/apps/arena/docs/src/bots/bots.md b/apps/arena/docs/src/bots/bots.md index dd212a4ed..f0b17f1d2 100644 --- a/apps/arena/docs/src/bots/bots.md +++ b/apps/arena/docs/src/bots/bots.md @@ -44,10 +44,6 @@ Formally, for a bot to reach this state, players need to be near it but not clos ![aggresive areas](bots_aggresive_areas.png) -#### Repositioning - -Bots will enter this state if they are stuck in a position where they cannot move, causing them to change their walking destination. - #### Running Away Bots will transition to this state whenever their health drops below a certain percentage. For now, this threshold is set at 40%. In this state, bots will attempt to escape from players by running in the opposite direction of the closest one. This does not necessarily mean they will run away from the player attacking them. diff --git a/apps/bot_manager/lib/bot_state_machine.ex b/apps/bot_manager/lib/bot_state_machine.ex index bf1a42360..e9ffa1170 100644 --- a/apps/bot_manager/lib/bot_state_machine.ex +++ b/apps/bot_manager/lib/bot_state_machine.ex @@ -64,9 +64,6 @@ defmodule BotManager.BotStateMachine do next_state = BotStateMachineChecker.move_to_next_state(bot_player, bot_state_machine, game_state.players) case next_state do - :repositioning -> - move(bot_player, bot_state_machine, game_state.zone.radius) - :moving -> move(bot_player, bot_state_machine, game_state.zone.radius) diff --git a/apps/bot_manager/lib/bot_state_machine_checker.ex b/apps/bot_manager/lib/bot_state_machine_checker.ex index 91aefa677..2693980f7 100644 --- a/apps/bot_manager/lib/bot_state_machine_checker.ex +++ b/apps/bot_manager/lib/bot_state_machine_checker.ex @@ -59,7 +59,7 @@ defmodule BotManager.BotStateMachineChecker do def move_to_next_state(bot_player, bot_state_machine, players) do cond do - bot_stuck?(bot_state_machine) -> :repositioning + bot_stuck?(bot_state_machine) -> :moving bot_health_low?(bot_player) -> :running_away bot_can_follow_a_player?(bot_player, bot_state_machine, players) -> :tracking_player bot_can_turn_aggresive?(bot_state_machine) -> :attacking From 383c8810f4a9af00322151e02b175ee5adcb5f2a Mon Sep 17 00:00:00 2001 From: tvillegas98 Date: Mon, 27 Jan 2025 15:23:52 -0300 Subject: [PATCH 12/14] fix: update position when the position is in the zone --- apps/bot_manager/lib/bot_state_machine.ex | 3 ++- apps/bot_manager/lib/utils.ex | 4 ++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/apps/bot_manager/lib/bot_state_machine.ex b/apps/bot_manager/lib/bot_state_machine.ex index e9ffa1170..31c2169b7 100644 --- a/apps/bot_manager/lib/bot_state_machine.ex +++ b/apps/bot_manager/lib/bot_state_machine.ex @@ -210,7 +210,8 @@ defmodule BotManager.BotStateMachine do defp determine_position_to_move_to(bot_state_machine, safe_zone_radius) do cond do - is_nil(bot_state_machine.position_to_move_to) -> + is_nil(bot_state_machine.position_to_move_to) || + not Utils.is_position_within_radius(bot_state_machine.position_to_move_to, safe_zone_radius) -> position_to_move_to = BotManager.Utils.random_position_within_safe_zone_radius(floor(safe_zone_radius)) Map.put(bot_state_machine, :position_to_move_to, position_to_move_to) diff --git a/apps/bot_manager/lib/utils.ex b/apps/bot_manager/lib/utils.ex index 3add9b98d..a4ad88887 100644 --- a/apps/bot_manager/lib/utils.ex +++ b/apps/bot_manager/lib/utils.ex @@ -16,6 +16,10 @@ defmodule BotManager.Utils do %{x: x, y: y} end + def is_position_within_radius(position, radius) do + Vector.distance(%{x: 0, y: 0}, position) <= radius + end + # This function will map the directions and distance from the bot to the players. def map_directions_to_players(players, bot_player, max_distance) do Map.delete(players, bot_player.id) From 532e6b1129837517a1646bfadcc540212cd742ba Mon Sep 17 00:00:00 2001 From: tvillegas98 Date: Mon, 27 Jan 2025 18:14:15 -0300 Subject: [PATCH 13/14] chore: credo asked for modularization --- apps/bot_manager/lib/bot_state_machine.ex | 113 ++++++++++++---------- apps/bot_manager/lib/utils.ex | 2 +- 2 files changed, 61 insertions(+), 54 deletions(-) diff --git a/apps/bot_manager/lib/bot_state_machine.ex b/apps/bot_manager/lib/bot_state_machine.ex index 31c2169b7..a7f95b499 100644 --- a/apps/bot_manager/lib/bot_state_machine.ex +++ b/apps/bot_manager/lib/bot_state_machine.ex @@ -25,41 +25,7 @@ defmodule BotManager.BotStateMachine do bot_state_machine: bot_state_machine, attack_blocked: attack_blocked }) do - bot_state_machine = - if is_nil(bot_state_machine.previous_position) do - bot_state_machine - |> Map.put(:previous_position, bot_player.position) - |> Map.put(:current_position, bot_player.position) - else - bot_state_machine - |> Map.put(:previous_position, bot_state_machine.current_position) - |> Map.put(:current_position, bot_player.position) - end - - %{distance: distance} = - Utils.get_distance_and_direction_to_positions( - bot_state_machine.previous_position, - bot_state_machine.current_position - ) - - bot_state_machine = - Map.put(bot_state_machine, :progress_for_basic_skill, bot_state_machine.progress_for_basic_skill + distance) - - bot_state_machine = - cond do - Vector.distance(bot_state_machine.previous_position, bot_state_machine.current_position) < 100 && - is_nil(bot_state_machine.start_time_stuck_in_position) -> - Map.put(bot_state_machine, :start_time_stuck_in_position, :os.system_time(:millisecond)) - |> Map.put(:stuck_in_position, bot_state_machine.current_position) - - not is_nil(bot_state_machine.stuck_in_position) && - Vector.distance(bot_state_machine.stuck_in_position, bot_state_machine.current_position) > 100 -> - Map.put(bot_state_machine, :start_time_stuck_in_position, nil) - |> Map.put(:stuck_in_position, nil) - - true -> - bot_state_machine - end + bot_state_machine = preprocess_bot_state(bot_state_machine, bot_player) next_state = BotStateMachineChecker.move_to_next_state(bot_player, bot_state_machine, game_state.players) @@ -79,23 +45,7 @@ defmodule BotManager.BotStateMachine do run_away(bot_player, game_state, bot_state_machine) :tracking_player -> - players_with_distances = - Utils.map_directions_to_players( - game_state.players, - bot_player, - bot_state_machine.max_vision_range_to_follow_player - ) - - if Enum.empty?(players_with_distances) do - move(bot_player, bot_state_machine, game_state.zone.radius) - else - closest_player = Enum.min_by(players_with_distances, & &1.distance) - - %{ - action: determine_player_move_action(bot_player, closest_player.direction), - bot_state_machine: bot_state_machine - } - end + track_player(game_state, bot_player, bot_state_machine) end end @@ -165,6 +115,63 @@ defmodule BotManager.BotStateMachine do end end + defp track_player(game_state, bot_player, bot_state_machine) do + players_with_distances = + Utils.map_directions_to_players( + game_state.players, + bot_player, + bot_state_machine.max_vision_range_to_follow_player + ) + + if Enum.empty?(players_with_distances) do + move(bot_player, bot_state_machine, game_state.zone.radius) + else + closest_player = Enum.min_by(players_with_distances, & &1.distance) + + %{ + action: determine_player_move_action(bot_player, closest_player.direction), + bot_state_machine: bot_state_machine + } + end + end + + defp preprocess_bot_state(bot_state_machine, bot_player) do + bot_state_machine = + if is_nil(bot_state_machine.previous_position) do + bot_state_machine + |> Map.put(:previous_position, bot_player.position) + |> Map.put(:current_position, bot_player.position) + else + bot_state_machine + |> Map.put(:previous_position, bot_state_machine.current_position) + |> Map.put(:current_position, bot_player.position) + end + + %{distance: distance} = + Utils.get_distance_and_direction_to_positions( + bot_state_machine.previous_position, + bot_state_machine.current_position + ) + + bot_state_machine = + Map.put(bot_state_machine, :progress_for_basic_skill, bot_state_machine.progress_for_basic_skill + distance) + + cond do + Vector.distance(bot_state_machine.previous_position, bot_state_machine.current_position) < 100 && + is_nil(bot_state_machine.start_time_stuck_in_position) -> + Map.put(bot_state_machine, :start_time_stuck_in_position, :os.system_time(:millisecond)) + |> Map.put(:stuck_in_position, bot_state_machine.current_position) + + not is_nil(bot_state_machine.stuck_in_position) && + Vector.distance(bot_state_machine.stuck_in_position, bot_state_machine.current_position) > 100 -> + Map.put(bot_state_machine, :start_time_stuck_in_position, nil) + |> Map.put(:stuck_in_position, nil) + + true -> + bot_state_machine + end + end + defp run_away(bot_player, game_state, bot_state_machine) do players_with_distances = Utils.map_directions_to_players(game_state, bot_player, bot_state_machine.vision_range_to_attack_player) @@ -211,7 +218,7 @@ defmodule BotManager.BotStateMachine do defp determine_position_to_move_to(bot_state_machine, safe_zone_radius) do cond do is_nil(bot_state_machine.position_to_move_to) || - not Utils.is_position_within_radius(bot_state_machine.position_to_move_to, safe_zone_radius) -> + not Utils.position_within_radius(bot_state_machine.position_to_move_to, safe_zone_radius) -> position_to_move_to = BotManager.Utils.random_position_within_safe_zone_radius(floor(safe_zone_radius)) Map.put(bot_state_machine, :position_to_move_to, position_to_move_to) diff --git a/apps/bot_manager/lib/utils.ex b/apps/bot_manager/lib/utils.ex index a4ad88887..ec29d12f3 100644 --- a/apps/bot_manager/lib/utils.ex +++ b/apps/bot_manager/lib/utils.ex @@ -16,7 +16,7 @@ defmodule BotManager.Utils do %{x: x, y: y} end - def is_position_within_radius(position, radius) do + def position_within_radius(position, radius) do Vector.distance(%{x: 0, y: 0}, position) <= radius end From 025850338702f1b177172be6f98b7f6acea2ce7b Mon Sep 17 00:00:00 2001 From: tvillegas98 Date: Wed, 29 Jan 2025 13:18:57 -0300 Subject: [PATCH 14/14] chore: modularize math functions and add norm function --- apps/bot_manager/lib/math/vector.ex | 6 +++++- apps/bot_manager/lib/utils.ex | 4 ++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/apps/bot_manager/lib/math/vector.ex b/apps/bot_manager/lib/math/vector.ex index 7019c5d75..7ac0aaaaa 100644 --- a/apps/bot_manager/lib/math/vector.ex +++ b/apps/bot_manager/lib/math/vector.ex @@ -43,8 +43,12 @@ defmodule BotManager.Math.Vector do } end + def norm(%{x: x, y: y}) do + :math.sqrt(:math.pow(x, 2) + :math.pow(y, 2)) + end + def normalize(%{x: x, y: y}) do - distance = :math.sqrt(:math.pow(x, 2) + :math.pow(y, 2)) + distance = norm(%{x: x, y: y}) %{ x: x / distance, diff --git a/apps/bot_manager/lib/utils.ex b/apps/bot_manager/lib/utils.ex index ec29d12f3..c3a32dc68 100644 --- a/apps/bot_manager/lib/utils.ex +++ b/apps/bot_manager/lib/utils.ex @@ -46,9 +46,9 @@ defmodule BotManager.Utils do def get_distance_and_direction_to_positions(base_position, end_position) do %{x: x, y: y} = Vector.sub(end_position, base_position) - distance = :math.sqrt(:math.pow(x, 2) + :math.pow(y, 2)) + distance = Vector.norm(%{x: x, y: y}) - direction = %{x: x / distance, y: y / distance} + direction = Vector.normalize(%{x: x, y: y}) %{ direction: direction,