From 3d720126cd2700db43082b14e963b594a3617479 Mon Sep 17 00:00:00 2001 From: EtienneDesticourt Date: Mon, 8 Aug 2016 13:12:15 +0200 Subject: [PATCH 01/20] Skeleton --- lib/entice/logic/ai/seek.ex | 50 ++++++++++++++++++++++++++++++++++++ lib/entice/logic/movement.ex | 8 +++--- 2 files changed, 55 insertions(+), 3 deletions(-) create mode 100644 lib/entice/logic/ai/seek.ex diff --git a/lib/entice/logic/ai/seek.ex b/lib/entice/logic/ai/seek.ex new file mode 100644 index 0000000..d36c890 --- /dev/null +++ b/lib/entice/logic/ai/seek.ex @@ -0,0 +1,50 @@ +defmodule Entice.Logic.Seek do + alias Entice.Logic.{Seek, Player.Position} + alias Entice.Entity.Coordination + alias Entice.Utils.Geom.Coord + + #TODO: Add old_target so it doesn't reaggro directly + #TODO: define on register + defstruct target: nil, aggro_distance: 10, escape_distance: 20 + + def register(entity), + do: Entity.put_behaviour(entity, Seek.Behaviour, []) + + def unregister(entity), + do: Entity.remove_behaviour(entity, Seek.Behaviour) + + defmodule Behaviour do + use Entice.Entity.Behaviour + + def init(entity, _args), + do: {:ok, entity |> put_attribute(%Seek{})} + + def handle_event({:movement_agent_updated, + %{player_entity: %Entity{attributes: %{Position => %Position{pos: mover_coord, plane: _}}} = player_entity, + channel_pid: channel_pid}}, + %Entity{attributes: %{Position => %Position{pos: my_coord, plane: _}, + Seek => %Seek{aggro_distance: aggro_distance, escape_distance: escape_distance, target: target}}} = entity) do + case target do + nil -> + if (calc_distance(my_coord, mover_coord) < aggro_distance) do + channel_pid |> send({:follow_target, player_entity}) + {:ok, entity |> update_attribute(Seek, fn(s) -> %Seek{s | target: player_entity} end)} + end + ^player_entity -> + if (calc_distance(my_coord, mover_coord) > escape_distance) do #TODO: calc distance between current pos and init pos instead + channel_pid |> send({:go_to, %Position{pos: %Coord{}, plane: 0}}) #TODO: Add init position for npc + {:ok, entity |> update_attribute(Seek, fn(s) -> %Seek{s | target: nil} end)} + end + _ -> {:ok, entity} + end + end + + def terminate(_reason, entity), + do: {:ok, entity |> remove_attribute(Seek)} + + defp calc_distance(coord1, coord2) do + #Not sure what %Coord looks like yet + 0 + end + end +end diff --git a/lib/entice/logic/movement.ex b/lib/entice/logic/movement.ex index c1d4430..0b398cb 100644 --- a/lib/entice/logic/movement.ex +++ b/lib/entice/logic/movement.ex @@ -1,7 +1,7 @@ defmodule Entice.Logic.Movement do alias Entice.Entity alias Entice.Utils.Geom.Coord - alias Entice.Logic.{Movement, Player.Position} + alias Entice.Logic.{Movement, MapInstance, Player.Position} @doc """ @@ -19,15 +19,17 @@ defmodule Entice.Logic.Movement do do: Entity.remove_behaviour(entity, Movement.Behaviour) - def update(entity, + def update(%Entity{attributes: %{MapInstance => %MapInstance{map: map, players: _}}} = entity, #TODO: Add Map instance attribute to players of the instance? %Position{} = new_pos, - %Movement{} = new_movement) do + %Movement{} = new_movement, + channel_pid) do #TODO: find a better way to pass movement channel to seek behaviour? entity |> Entity.attribute_transaction( fn attrs -> attrs |> Map.put(Position, new_pos) |> Map.put(Movement, new_movement) end) + Coordination.notify_all(map, {:movement_agent_updated, %{player_entity: entity, channel_pid: channel_pid}}) end From e9913c3549c9b9b26597c300f0421b05351a790e Mon Sep 17 00:00:00 2001 From: EtienneDesticourt Date: Tue, 9 Aug 2016 19:13:25 +0200 Subject: [PATCH 02/20] Seek logic rework, removed channel callback --- lib/entice/logic/ai/seek.ex | 67 ++++++++++++++++++----------- lib/entice/logic/movement.ex | 5 +-- lib/entice/logic/npc.ex | 9 ++-- lib/entice/logic/player.ex | 1 + test/entice/logic/ai/seek_tests.exs | 42 ++++++++++++++++++ 5 files changed, 93 insertions(+), 31 deletions(-) create mode 100644 test/entice/logic/ai/seek_tests.exs diff --git a/lib/entice/logic/ai/seek.ex b/lib/entice/logic/ai/seek.ex index d36c890..5ad0b48 100644 --- a/lib/entice/logic/ai/seek.ex +++ b/lib/entice/logic/ai/seek.ex @@ -1,50 +1,69 @@ defmodule Entice.Logic.Seek do - alias Entice.Logic.{Seek, Player.Position} - alias Entice.Entity.Coordination + alias Entice.Logic.{Seek, Player.Position, Npc, Movement} alias Entice.Utils.Geom.Coord - #TODO: Add old_target so it doesn't reaggro directly - #TODO: define on register defstruct target: nil, aggro_distance: 10, escape_distance: 20 def register(entity), do: Entity.put_behaviour(entity, Seek.Behaviour, []) + def register(entity, aggro_distance, escape_distance) + when is_integer(aggro_distance) and is_integer(escape_distance), + do: Entity.put_behaviour(entity, Seek.Behaviour, %{aggro_distance: aggro_distance, escape_distance: escape_distance}) + def unregister(entity), do: Entity.remove_behaviour(entity, Seek.Behaviour) + #TODO: Add map instance to all map instance agents + #TODO: Add team attr to determine who should be attacked by whom defmodule Behaviour do use Entice.Entity.Behaviour + def init(entity, %{aggro_distance: aggro_distance, escape_distance: escape_distance}), + do: {:ok, entity |> put_attribute(%Seek{aggro_distance: aggro_distance, escape_distance: escape_distance})} + def init(entity, _args), do: {:ok, entity |> put_attribute(%Seek{})} def handle_event({:movement_agent_updated, - %{player_entity: %Entity{attributes: %{Position => %Position{pos: mover_coord, plane: _}}} = player_entity, - channel_pid: channel_pid}}, - %Entity{attributes: %{Position => %Position{pos: my_coord, plane: _}, - Seek => %Seek{aggro_distance: aggro_distance, escape_distance: escape_distance, target: target}}} = entity) do - case target do - nil -> - if (calc_distance(my_coord, mover_coord) < aggro_distance) do - channel_pid |> send({:follow_target, player_entity}) - {:ok, entity |> update_attribute(Seek, fn(s) -> %Seek{s | target: player_entity} end)} - end - ^player_entity -> - if (calc_distance(my_coord, mover_coord) > escape_distance) do #TODO: calc distance between current pos and init pos instead - channel_pid |> send({:go_to, %Position{pos: %Coord{}, plane: 0}}) #TODO: Add init position for npc - {:ok, entity |> update_attribute(Seek, fn(s) -> %Seek{s | target: nil} end)} - end - _ -> {:ok, entity} - end + %Entity{attributes: %{Position => %Position{pos: mover_pos, plane: _}}} = other_entity}, + %Entity{attributes: %{Position => %Position{pos: my_pos, plane: _}, + Movement => _, + Npc => %Npc{npc_model_id: _, init_pos: init_pos}, + Seek => %Seek{aggro_distance: aggro_distance, escape_distance: escape_distance, target: target}}} = entity) do + unless other_entity == entity do + case target do + nil -> + if calc_distance(my_pos, mover_pos) < aggro_distance do + {:ok, entity |> update_attribute(Seek, fn(s) -> %Seek{s | target: other_entity} end) + |> update_attribute(Movement, fn(m) -> %Movement{m | goal: mover_pos} end) } + end + + ^other_entity -> + current_distance = calc_distance(my_pos, mover_pos) + entity = cond do + current_distance >= escape_distance -> + entity + |> update_attribute(Seek, fn(s) -> %Seek{s | target: nil} end) + |> update_attribute(Movement, fn(m) -> %Movement{m | goal: init_pos} end) + true -> + entity + |> update_attribute(Movement, fn(m) -> %Movement{m | goal: mover_pos} end) + end + {:ok, entity} + + _ -> {:ok, entity} + end + end + {:ok, entity} end def terminate(_reason, entity), do: {:ok, entity |> remove_attribute(Seek)} - defp calc_distance(coord1, coord2) do - #Not sure what %Coord looks like yet - 0 + #TODO: Should probably move to Coord in Utils + defp calc_distance(%Coord{x: x1, y: y1}, %Coord{x: x2, y: y2}) do + :math.sqrt(:math.pow((x2-x1), 2) + :math.pow((y2-y1), 2)) end end end diff --git a/lib/entice/logic/movement.ex b/lib/entice/logic/movement.ex index 0b398cb..3423d83 100644 --- a/lib/entice/logic/movement.ex +++ b/lib/entice/logic/movement.ex @@ -21,15 +21,14 @@ defmodule Entice.Logic.Movement do def update(%Entity{attributes: %{MapInstance => %MapInstance{map: map, players: _}}} = entity, #TODO: Add Map instance attribute to players of the instance? %Position{} = new_pos, - %Movement{} = new_movement, - channel_pid) do #TODO: find a better way to pass movement channel to seek behaviour? + %Movement{} = new_movement) do entity |> Entity.attribute_transaction( fn attrs -> attrs |> Map.put(Position, new_pos) |> Map.put(Movement, new_movement) end) - Coordination.notify_all(map, {:movement_agent_updated, %{player_entity: entity, channel_pid: channel_pid}}) + Coordination.notify_all(map, {:movement_agent_updated, entity}) end diff --git a/lib/entice/logic/npc.ex b/lib/entice/logic/npc.ex index f46df98..d36c56b 100644 --- a/lib/entice/logic/npc.ex +++ b/lib/entice/logic/npc.ex @@ -4,15 +4,16 @@ defmodule Entice.Logic.Npc do alias Entice.Logic.{Npc, Vitals} alias Entice.Logic.Player.{Name, Position, Level} + defstruct npc_model_id: :dhuum, init_pos: %Position{} - defstruct(npc_model_id: :dhuum) - - def spawn(name, model, %Position{} = position) + def spawn(name, model, %Position{} = position, %{seeks: seeks} \\ %{seeks: false}) when is_binary(name) and is_atom(model) do {:ok, id, pid} = Entity.start() Npc.register(id, name, model, position) Vitals.register(id) + Movement.register(id) + #if seeks, do: Seek.register(id) {:ok, id, pid} end @@ -23,7 +24,7 @@ defmodule Entice.Logic.Npc do attrs |> Map.put(Name, %Name{name: name}) |> Map.put(Position, position) - |> Map.put(Npc, %Npc{npc_model_id: model}) + |> Map.put(Npc, %Npc{npc_model_id: model, init_pos: position}) |> Map.put(Level, %Level{level: 20}) end) end diff --git a/lib/entice/logic/player.ex b/lib/entice/logic/player.ex index d3b5648..ca45d16 100644 --- a/lib/entice/logic/player.ex +++ b/lib/entice/logic/player.ex @@ -9,6 +9,7 @@ defmodule Entice.Logic.Player do defmodule Name, do: defstruct( name: "Unknown Entity") + #TODO: rename :pos to :coord ? Confusing sometimes defmodule Position, do: defstruct( pos: %Coord{}, plane: 1) diff --git a/test/entice/logic/ai/seek_tests.exs b/test/entice/logic/ai/seek_tests.exs new file mode 100644 index 0000000..c74bff6 --- /dev/null +++ b/test/entice/logic/ai/seek_tests.exs @@ -0,0 +1,42 @@ +defmodule Entice.Logic.SeekTest do + use ExUnit.Case, async: true + use Entice.Logic.Maps + use Entice.Logic.Attributes + alias Entice.Entity + alias Entice.Logic.{Player, Seek} + alias Entice.Entity.Coordination + + + setup do + {:ok, _, npc_pid} = Npc.spawn("Dhuum", :dhuum, %Position{pos: %Coord{x: 10, y: 10}}) + {:ok, _, player_pid} = Entity.start + Player.register(player_pid, HeroesAscent) + {:ok, [npc_entity: npc_pid, player_entity: player_pid]} + end + + test "update same entity", %{npc_entity: npc_pid, player_entity: player_pid} do + Coordination.notify(npc_pid, {:movement_agent_updated, npc_pid}) + assert {:ok, %Seek{target: nil}} = Entity.fetch_attribute(npc_pid, Seek) + end + + test "update no target, entity close enough to aggro", %{npc_entity: npc_pid, player_entity: player_pid} do + #player_pid |> update_attribute(Position, fn(p) -> %Position{p | pos: %Coord{x: 10, y: 10}} end) + assert true + end + + test "update no target, entity too far to aggro", %{npc_entity: npc_pid, player_entity: player_pid} do + + end + + test "update has target, entity is not current target", %{npc_entity: npc_pid, player_entity: player_pid} do + + end + + test "update has target, entity is current target, entity escapes", %{npc_entity: npc_pid, player_entity: player_pid} do + + end + + test "update has target, entity is current target, entity does not escape", %{npc_entity: npc_pid, player_entity: player_pid} do + + end +end From 59fa130081d0b4e21620cd099fb3febe5ff450a3 Mon Sep 17 00:00:00 2001 From: EtienneDesticourt Date: Wed, 10 Aug 2016 02:26:22 +0200 Subject: [PATCH 03/20] Fixed seek logic and added tests, fixed movement update, added ref to instance in agents --- lib/entice/logic/ai/seek.ex | 60 +++++++++++------------ lib/entice/logic/map_instance.ex | 4 +- lib/entice/logic/movement.ex | 7 ++- lib/entice/logic/npc.ex | 10 ++-- test/entice/logic/ai/seek_test.exs | 74 +++++++++++++++++++++++++++++ test/entice/logic/ai/seek_tests.exs | 42 ---------------- test/entice/logic/movement_test.exs | 14 ++++-- 7 files changed, 128 insertions(+), 83 deletions(-) create mode 100644 test/entice/logic/ai/seek_test.exs delete mode 100644 test/entice/logic/ai/seek_tests.exs diff --git a/lib/entice/logic/ai/seek.ex b/lib/entice/logic/ai/seek.ex index 5ad0b48..ff4592f 100644 --- a/lib/entice/logic/ai/seek.ex +++ b/lib/entice/logic/ai/seek.ex @@ -1,4 +1,5 @@ defmodule Entice.Logic.Seek do + alias Entice.Entity alias Entice.Logic.{Seek, Player.Position, Npc, Movement} alias Entice.Utils.Geom.Coord @@ -14,7 +15,6 @@ defmodule Entice.Logic.Seek do def unregister(entity), do: Entity.remove_behaviour(entity, Seek.Behaviour) - #TODO: Add map instance to all map instance agents #TODO: Add team attr to determine who should be attacked by whom defmodule Behaviour do use Entice.Entity.Behaviour @@ -25,37 +25,37 @@ defmodule Entice.Logic.Seek do def init(entity, _args), do: {:ok, entity |> put_attribute(%Seek{})} - def handle_event({:movement_agent_updated, - %Entity{attributes: %{Position => %Position{pos: mover_pos, plane: _}}} = other_entity}, - %Entity{attributes: %{Position => %Position{pos: my_pos, plane: _}, - Movement => _, - Npc => %Npc{npc_model_id: _, init_pos: init_pos}, - Seek => %Seek{aggro_distance: aggro_distance, escape_distance: escape_distance, target: target}}} = entity) do - unless other_entity == entity do - case target do - nil -> - if calc_distance(my_pos, mover_pos) < aggro_distance do - {:ok, entity |> update_attribute(Seek, fn(s) -> %Seek{s | target: other_entity} end) - |> update_attribute(Movement, fn(m) -> %Movement{m | goal: mover_pos} end) } - end - - ^other_entity -> - current_distance = calc_distance(my_pos, mover_pos) - entity = cond do - current_distance >= escape_distance -> - entity - |> update_attribute(Seek, fn(s) -> %Seek{s | target: nil} end) - |> update_attribute(Movement, fn(m) -> %Movement{m | goal: init_pos} end) - true -> - entity - |> update_attribute(Movement, fn(m) -> %Movement{m | goal: mover_pos} end) - end + #No introspection for npcs ;) + def handle_event({:movement_agent_updated, %Position{pos: _}, other_entity_id}, %Entity{id: id} = entity) + when other_entity_id == id, + do: {:ok, entity} + + def handle_event({:movement_agent_updated, %Position{pos: mover_pos}, other_entity_id}, + %Entity{attributes: %{Position => %Position{pos: my_pos}, + Movement => _, + Npc => %Npc{init_pos: init_pos}, + Seek => %Seek{aggro_distance: aggro_distance, escape_distance: escape_distance, target: target}}} = entity) do + case target do + nil -> + if calc_distance(my_pos, mover_pos) < aggro_distance do + {:ok, entity |> update_attribute(Seek, fn(s) -> %Seek{s | target: other_entity_id} end) + |> update_attribute(Movement, fn(m) -> %Movement{m | goal: mover_pos} end)} + else {:ok, entity} + end + + ^other_entity_id -> + if calc_distance(init_pos, mover_pos) >= escape_distance do + {:ok, entity + |> update_attribute(Seek, fn(s) -> %Seek{s | target: nil} end) + |> update_attribute(Movement, fn(m) -> %Movement{m | goal: init_pos} end)} + else + {:ok, entity + |> update_attribute(Movement, fn(m) -> %Movement{m | goal: mover_pos} end)} + end - _ -> {:ok, entity} - end - end - {:ok, entity} + _ -> {:ok, entity} + end end def terminate(_reason, entity), diff --git a/lib/entice/logic/map_instance.ex b/lib/entice/logic/map_instance.ex index 588310c..20946f3 100644 --- a/lib/entice/logic/map_instance.ex +++ b/lib/entice/logic/map_instance.ex @@ -44,6 +44,7 @@ defmodule Entice.Logic.MapInstance do def handle_event( {:map_instance_player_add, player_entity}, %Entity{attributes: %{MapInstance => %MapInstance{map: map, players: players}}} = entity) do + player_entity |> Entity.attribute_transaction(fn attrs -> attrs |> Map.put(MapInstance, %MapInstance{map: map}) end) Coordination.register(player_entity, map) # TODO change map to something else if we have multiple instances {:ok, entity |> update_attribute(MapInstance, fn(m) -> %MapInstance{m | players: players+1} end)} end @@ -51,7 +52,8 @@ defmodule Entice.Logic.MapInstance do def handle_event( {:map_instance_npc_add, %{name: name, model: model, position: position}}, %Entity{attributes: %{MapInstance => %MapInstance{map: map}}} = entity) do - {:ok, eid, _pid} = Npc.spawn(name, model, position) + {:ok, eid, _pid} = Npc.spawn(name, model, position) + eid |> Entity.attribute_transaction(fn attrs -> attrs |> Map.put(MapInstance, %MapInstance{map: map}) end) Coordination.register(eid, map) # TODO change map to something else if we have multiple instances {:ok, entity} end diff --git a/lib/entice/logic/movement.ex b/lib/entice/logic/movement.ex index 3423d83..d2aaa63 100644 --- a/lib/entice/logic/movement.ex +++ b/lib/entice/logic/movement.ex @@ -1,5 +1,6 @@ defmodule Entice.Logic.Movement do alias Entice.Entity + alias Entice.Entity.Coordination alias Entice.Utils.Geom.Coord alias Entice.Logic.{Movement, MapInstance, Player.Position} @@ -19,7 +20,7 @@ defmodule Entice.Logic.Movement do do: Entity.remove_behaviour(entity, Movement.Behaviour) - def update(%Entity{attributes: %{MapInstance => %MapInstance{map: map, players: _}}} = entity, #TODO: Add Map instance attribute to players of the instance? + def update(entity, %Position{} = new_pos, %Movement{} = new_movement) do entity |> Entity.attribute_transaction( @@ -28,7 +29,9 @@ defmodule Entice.Logic.Movement do |> Map.put(Position, new_pos) |> Map.put(Movement, new_movement) end) - Coordination.notify_all(map, {:movement_agent_updated, entity}) + {:ok, %MapInstance{map: map, players: _}} = Entity.fetch_attribute(entity, MapInstance) + #Can't figure out how to get eid from pid + Coordination.notify_all(map, {:movement_agent_updated, new_pos, entity}) #TODO: Change to use eid instead of pid for self seeking guard in seek l30 end diff --git a/lib/entice/logic/npc.ex b/lib/entice/logic/npc.ex index d36c56b..9b86de8 100644 --- a/lib/entice/logic/npc.ex +++ b/lib/entice/logic/npc.ex @@ -1,19 +1,19 @@ defmodule Entice.Logic.Npc do use Entice.Logic.Map alias Entice.Entity - alias Entice.Logic.{Npc, Vitals} + alias Entice.Logic.{Npc, Vitals, Movement, Seek} alias Entice.Logic.Player.{Name, Position, Level} defstruct npc_model_id: :dhuum, init_pos: %Position{} - def spawn(name, model, %Position{} = position, %{seeks: seeks} \\ %{seeks: false}) + def spawn(name, model, %Position{} = position, %{seeks: seeks} \\ %{seeks: true}) when is_binary(name) and is_atom(model) do {:ok, id, pid} = Entity.start() Npc.register(id, name, model, position) Vitals.register(id) - Movement.register(id) - #if seeks, do: Seek.register(id) + Movement.register(id) + if seeks, do: Seek.register(id) {:ok, id, pid} end @@ -24,7 +24,7 @@ defmodule Entice.Logic.Npc do attrs |> Map.put(Name, %Name{name: name}) |> Map.put(Position, position) - |> Map.put(Npc, %Npc{npc_model_id: model, init_pos: position}) + |> Map.put(Npc, %Npc{npc_model_id: model, init_pos: position.pos}) |> Map.put(Level, %Level{level: 20}) end) end diff --git a/test/entice/logic/ai/seek_test.exs b/test/entice/logic/ai/seek_test.exs new file mode 100644 index 0000000..79d869e --- /dev/null +++ b/test/entice/logic/ai/seek_test.exs @@ -0,0 +1,74 @@ +defmodule Entice.Logic.SeekTest do + use ExUnit.Case, async: true + use Entice.Logic.Maps + use Entice.Logic.Attributes + alias Entice.Entity + alias Entice.Logic.{Player, Seek} + alias Entice.Logic.Player.Position + alias Entice.Entity.Coordination + + + setup do + {:ok, npc_eid, npc_pid} = Npc.spawn("Dhuum", :dhuum, %Position{pos: %Coord{x: 10, y: 10}}) + {:ok, player_eid, player_pid} = Entity.start + Player.register(player_pid, HeroesAscent) + {:ok, [npc_entity: npc_pid, npc_eid: npc_eid, player_eid: player_eid, player_entity: player_pid]} + end + + test "correct default register", %{npc_entity: pid} do + assert {:ok, %Seek{target: nil, aggro_distance: 10, escape_distance: 20}} = Entity.fetch_attribute(pid, Seek) + end + + test "update same entity", %{npc_entity: pid, npc_eid: eid} do + Coordination.notify(pid, {:movement_agent_updated, %Position{pos: %Coord{x: 1, y: 2}}, eid}) + assert {:ok, %Seek{target: nil}} = Entity.fetch_attribute(pid, Seek) + end + + test "update no target, entity close enough to aggro", %{npc_entity: npc_pid, player_eid: player_eid} do + #Move player within 2 units of distance of npc with aggro distance of 10 (default) + mover_pos = %Coord{x: 14, y: 10} + Coordination.notify(npc_pid, {:movement_agent_updated, %Position{pos: mover_pos}, player_eid}) + + assert {:ok, %Seek{target: ^player_eid}} = Entity.fetch_attribute(npc_pid, Seek) + assert {:ok, %Movement{goal: ^mover_pos}} = Entity.fetch_attribute(npc_pid, Movement) + end + + test "update no target, entity too far to aggro", %{npc_entity: npc_pid, player_eid: player_eid} do + Coordination.notify(npc_pid, {:movement_agent_updated, %Position{pos: %Coord{x: 19, y: 15}}, player_eid}) + assert {:ok, %Seek{target: nil}} = Entity.fetch_attribute(npc_pid, Seek) + end + + test "update has target, entity is not current target", %{npc_entity: npc_pid, player_eid: player_eid} do + #Set player as target + Entity.put_attribute(npc_pid, %Seek{target: player_eid, aggro_distance: 10, escape_distance: 20}) + + #Create unrelated entity + {:ok, unknown_eid, _} = Entity.start + + Coordination.notify(npc_pid, {:movement_agent_updated, %Position{pos: %Coord{x: 11, y: 11}}, unknown_eid}) + assert {:ok, %Seek{target: ^player_eid}} = Entity.fetch_attribute(npc_pid, Seek) + end + + test "update has target, entity is current target, entity escapes", %{npc_entity: npc_pid, player_eid: player_eid} do + init_pos = %Coord{x: 10, y: 10} + + #Set player as target + Entity.put_attribute(npc_pid, %Seek{target: player_eid, aggro_distance: 10, escape_distance: 20}) + + #Notify of new position outside escape range (dist = 20.6 > 20) + Coordination.notify(npc_pid, {:movement_agent_updated, %Position{pos: %Coord{x: 30, y: 15}}, player_eid}) + assert {:ok, %Seek{target: nil}} = Entity.fetch_attribute(npc_pid, Seek) + assert {:ok, %Movement{goal: ^init_pos}} = Entity.fetch_attribute(npc_pid, Movement) + end + + test "update has target, entity is current target, entity does not escape", %{npc_entity: npc_pid, player_eid: player_eid} do + #Set player as target + Entity.put_attribute(npc_pid, %Seek{target: player_eid, aggro_distance: 10, escape_distance: 20}) + + #Notify of new position outside escape range (dist = 20.6 > 20) + new_player_pos = %Coord{x: 14, y: 15} + Coordination.notify(npc_pid, {:movement_agent_updated, %Position{pos: new_player_pos}, player_eid}) + assert {:ok, %Seek{target: ^player_eid}} = Entity.fetch_attribute(npc_pid, Seek) + assert {:ok, %Movement{goal: ^new_player_pos}} = Entity.fetch_attribute(npc_pid, Movement) + end +end diff --git a/test/entice/logic/ai/seek_tests.exs b/test/entice/logic/ai/seek_tests.exs deleted file mode 100644 index c74bff6..0000000 --- a/test/entice/logic/ai/seek_tests.exs +++ /dev/null @@ -1,42 +0,0 @@ -defmodule Entice.Logic.SeekTest do - use ExUnit.Case, async: true - use Entice.Logic.Maps - use Entice.Logic.Attributes - alias Entice.Entity - alias Entice.Logic.{Player, Seek} - alias Entice.Entity.Coordination - - - setup do - {:ok, _, npc_pid} = Npc.spawn("Dhuum", :dhuum, %Position{pos: %Coord{x: 10, y: 10}}) - {:ok, _, player_pid} = Entity.start - Player.register(player_pid, HeroesAscent) - {:ok, [npc_entity: npc_pid, player_entity: player_pid]} - end - - test "update same entity", %{npc_entity: npc_pid, player_entity: player_pid} do - Coordination.notify(npc_pid, {:movement_agent_updated, npc_pid}) - assert {:ok, %Seek{target: nil}} = Entity.fetch_attribute(npc_pid, Seek) - end - - test "update no target, entity close enough to aggro", %{npc_entity: npc_pid, player_entity: player_pid} do - #player_pid |> update_attribute(Position, fn(p) -> %Position{p | pos: %Coord{x: 10, y: 10}} end) - assert true - end - - test "update no target, entity too far to aggro", %{npc_entity: npc_pid, player_entity: player_pid} do - - end - - test "update has target, entity is not current target", %{npc_entity: npc_pid, player_entity: player_pid} do - - end - - test "update has target, entity is current target, entity escapes", %{npc_entity: npc_pid, player_entity: player_pid} do - - end - - test "update has target, entity is current target, entity does not escape", %{npc_entity: npc_pid, player_entity: player_pid} do - - end -end diff --git a/test/entice/logic/movement_test.exs b/test/entice/logic/movement_test.exs index c49418a..b5ef656 100644 --- a/test/entice/logic/movement_test.exs +++ b/test/entice/logic/movement_test.exs @@ -1,10 +1,13 @@ defmodule Entice.Logic.MovementTest do use ExUnit.Case, async: true alias Entice.Entity + alias Entice.Entity.Coordination alias Entice.Utils.Geom.Coord - alias Entice.Logic.Movement + alias Entice.Logic.{Movement, MapInstance} alias Entice.Logic.Player.Position + use Entice.Logic.Map + defmap TestMap1 setup do {:ok, _id, pid} = Entity.start @@ -38,11 +41,16 @@ defmodule Entice.Logic.MovementTest do test "update", %{entity: pid} do + Entity.put_attribute(pid, %MapInstance{map: TestMap1}) + Coordination.register_observer(self, TestMap1) + + new_pos = %Position{pos: %Coord{x: 42, y: 1337}, plane: 7} Movement.update(pid, - %Position{pos: %Coord{x: 42, y: 1337}, plane: 7}, + new_pos, %Movement{goal: %Coord{x: 1337, y: 42}, plane: 13, move_type: 5, velocity: 0.5}) assert {:ok, %Position{plane: 7}} = Entity.fetch_attribute(pid, Position) - assert {:ok, %Movement{move_type: 5}} = Entity.fetch_attribute(pid, Movement) + assert {:ok, %Movement{move_type: 5}} = Entity.fetch_attribute(pid, Movement) + assert_receive {:movement_agent_updated, ^new_pos, ^pid} #TODO: update to use eid end From 3792cbd470b7ff9b8dc1178b3f8eeb2a2ed1272a Mon Sep 17 00:00:00 2001 From: EtienneDesticourt Date: Wed, 10 Aug 2016 02:32:40 +0200 Subject: [PATCH 04/20] Minor rename for clarity --- lib/entice/logic/ai/seek.ex | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/entice/logic/ai/seek.ex b/lib/entice/logic/ai/seek.ex index ff4592f..cc99680 100644 --- a/lib/entice/logic/ai/seek.ex +++ b/lib/entice/logic/ai/seek.ex @@ -26,11 +26,11 @@ defmodule Entice.Logic.Seek do do: {:ok, entity |> put_attribute(%Seek{})} #No introspection for npcs ;) - def handle_event({:movement_agent_updated, %Position{pos: _}, other_entity_id}, %Entity{id: id} = entity) - when other_entity_id == id, + def handle_event({:movement_agent_updated, %Position{pos: _}, moving_entity_id}, %Entity{id: my_id} = entity) + when moving_entity_id == my_id, do: {:ok, entity} - def handle_event({:movement_agent_updated, %Position{pos: mover_pos}, other_entity_id}, + def handle_event({:movement_agent_updated, %Position{pos: mover_pos}, moving_entity_id}, %Entity{attributes: %{Position => %Position{pos: my_pos}, Movement => _, Npc => %Npc{init_pos: init_pos}, @@ -38,13 +38,13 @@ defmodule Entice.Logic.Seek do case target do nil -> if calc_distance(my_pos, mover_pos) < aggro_distance do - {:ok, entity |> update_attribute(Seek, fn(s) -> %Seek{s | target: other_entity_id} end) + {:ok, entity |> update_attribute(Seek, fn(s) -> %Seek{s | target: moving_entity_id} end) |> update_attribute(Movement, fn(m) -> %Movement{m | goal: mover_pos} end)} else {:ok, entity} end - ^other_entity_id -> + ^moving_entity_id -> if calc_distance(init_pos, mover_pos) >= escape_distance do {:ok, entity |> update_attribute(Seek, fn(s) -> %Seek{s | target: nil} end) From 1fd1a236d060c7f489c203faf72ff6826c3da57b Mon Sep 17 00:00:00 2001 From: EtienneDesticourt Date: Wed, 10 Aug 2016 17:25:49 +0200 Subject: [PATCH 05/20] Modified Seek to listen for :entity_change on Position --- lib/entice/logic/ai/seek.ex | 9 ++--- lib/entice/logic/map_instance.ex | 2 - lib/entice/logic/movement.ex | 6 +-- test/entice/logic/ai/seek_test.exs | 63 +++++++++++++++++++---------- test/entice/logic/movement_test.exs | 14 ++----- 5 files changed, 50 insertions(+), 44 deletions(-) diff --git a/lib/entice/logic/ai/seek.ex b/lib/entice/logic/ai/seek.ex index cc99680..b9a0d72 100644 --- a/lib/entice/logic/ai/seek.ex +++ b/lib/entice/logic/ai/seek.ex @@ -22,16 +22,15 @@ defmodule Entice.Logic.Seek do def init(entity, %{aggro_distance: aggro_distance, escape_distance: escape_distance}), do: {:ok, entity |> put_attribute(%Seek{aggro_distance: aggro_distance, escape_distance: escape_distance})} - def init(entity, _args), + def init(entity, _args), do: {:ok, entity |> put_attribute(%Seek{})} #No introspection for npcs ;) - def handle_event({:movement_agent_updated, %Position{pos: _}, moving_entity_id}, %Entity{id: my_id} = entity) - when moving_entity_id == my_id, + def handle_event({:entity_change, %{entity_id: eid}}, %Entity{id: eid} = entity), do: {:ok, entity} - def handle_event({:movement_agent_updated, %Position{pos: mover_pos}, moving_entity_id}, - %Entity{attributes: %{Position => %Position{pos: my_pos}, + def handle_event({:entity_change, %{changed: %{Position => %Position{pos: mover_pos}}, entity_id: moving_entity_id}}, + %Entity{attributes: %{Position => %Position{pos: my_pos}, Movement => _, Npc => %Npc{init_pos: init_pos}, Seek => %Seek{aggro_distance: aggro_distance, escape_distance: escape_distance, target: target}}} = entity) do diff --git a/lib/entice/logic/map_instance.ex b/lib/entice/logic/map_instance.ex index 20946f3..cbcf047 100644 --- a/lib/entice/logic/map_instance.ex +++ b/lib/entice/logic/map_instance.ex @@ -44,7 +44,6 @@ defmodule Entice.Logic.MapInstance do def handle_event( {:map_instance_player_add, player_entity}, %Entity{attributes: %{MapInstance => %MapInstance{map: map, players: players}}} = entity) do - player_entity |> Entity.attribute_transaction(fn attrs -> attrs |> Map.put(MapInstance, %MapInstance{map: map}) end) Coordination.register(player_entity, map) # TODO change map to something else if we have multiple instances {:ok, entity |> update_attribute(MapInstance, fn(m) -> %MapInstance{m | players: players+1} end)} end @@ -53,7 +52,6 @@ defmodule Entice.Logic.MapInstance do {:map_instance_npc_add, %{name: name, model: model, position: position}}, %Entity{attributes: %{MapInstance => %MapInstance{map: map}}} = entity) do {:ok, eid, _pid} = Npc.spawn(name, model, position) - eid |> Entity.attribute_transaction(fn attrs -> attrs |> Map.put(MapInstance, %MapInstance{map: map}) end) Coordination.register(eid, map) # TODO change map to something else if we have multiple instances {:ok, entity} end diff --git a/lib/entice/logic/movement.ex b/lib/entice/logic/movement.ex index d2aaa63..c1d4430 100644 --- a/lib/entice/logic/movement.ex +++ b/lib/entice/logic/movement.ex @@ -1,8 +1,7 @@ defmodule Entice.Logic.Movement do alias Entice.Entity - alias Entice.Entity.Coordination alias Entice.Utils.Geom.Coord - alias Entice.Logic.{Movement, MapInstance, Player.Position} + alias Entice.Logic.{Movement, Player.Position} @doc """ @@ -29,9 +28,6 @@ defmodule Entice.Logic.Movement do |> Map.put(Position, new_pos) |> Map.put(Movement, new_movement) end) - {:ok, %MapInstance{map: map, players: _}} = Entity.fetch_attribute(entity, MapInstance) - #Can't figure out how to get eid from pid - Coordination.notify_all(map, {:movement_agent_updated, new_pos, entity}) #TODO: Change to use eid instead of pid for self seeking guard in seek l30 end diff --git a/test/entice/logic/ai/seek_test.exs b/test/entice/logic/ai/seek_test.exs index 79d869e..487771b 100644 --- a/test/entice/logic/ai/seek_test.exs +++ b/test/entice/logic/ai/seek_test.exs @@ -6,35 +6,44 @@ defmodule Entice.Logic.SeekTest do alias Entice.Logic.{Player, Seek} alias Entice.Logic.Player.Position alias Entice.Entity.Coordination + use Entice.Logic.Map + defmap TestMap setup do - {:ok, npc_eid, npc_pid} = Npc.spawn("Dhuum", :dhuum, %Position{pos: %Coord{x: 10, y: 10}}) + npc_init_pos = %Coord{x: 10, y: 10} + {:ok, npc_eid, npc_pid} = Npc.spawn("Dhuum", :dhuum, %Position{pos: npc_init_pos}) {:ok, player_eid, player_pid} = Entity.start - Player.register(player_pid, HeroesAscent) - {:ok, [npc_entity: npc_pid, npc_eid: npc_eid, player_eid: player_eid, player_entity: player_pid]} + #Sets an initial pos for the player so the Position attr appears as changed and not added in the following tests + simulate_movement_update(player_pid, %Position{pos: %Coord{x: 1, y: 2}}, %Movement{goal: %Coord{x: 3, y: 4}}) + + Player.register(player_pid, TestMap) + Coordination.register(player_eid, TestMap) + Coordination.register(npc_eid, TestMap) + {:ok, [npc_entity: npc_pid, npc_eid: npc_eid, player_eid: player_eid, player_entity: player_pid, npc_init_pos: npc_init_pos]} end test "correct default register", %{npc_entity: pid} do assert {:ok, %Seek{target: nil, aggro_distance: 10, escape_distance: 20}} = Entity.fetch_attribute(pid, Seek) end - test "update same entity", %{npc_entity: pid, npc_eid: eid} do - Coordination.notify(pid, {:movement_agent_updated, %Position{pos: %Coord{x: 1, y: 2}}, eid}) + test "update same entity", %{npc_entity: pid} do + simulate_movement_update(pid, %Position{pos: %Coord{x: 1, y: 2}}, %Movement{goal: %Coord{x: 3, y: 4}}) assert {:ok, %Seek{target: nil}} = Entity.fetch_attribute(pid, Seek) end - test "update no target, entity close enough to aggro", %{npc_entity: npc_pid, player_eid: player_eid} do + test "update no target, entity close enough to aggro", %{npc_entity: npc_pid, player_eid: player_eid, player_entity: player_pid} do #Move player within 2 units of distance of npc with aggro distance of 10 (default) mover_pos = %Coord{x: 14, y: 10} - Coordination.notify(npc_pid, {:movement_agent_updated, %Position{pos: mover_pos}, player_eid}) + simulate_movement_update(player_pid, %Position{pos: mover_pos}, %Movement{goal: mover_pos}) + assert {:ok, %Seek{target: ^player_eid}} = Entity.fetch_attribute(npc_pid, Seek) assert {:ok, %Movement{goal: ^mover_pos}} = Entity.fetch_attribute(npc_pid, Movement) end - test "update no target, entity too far to aggro", %{npc_entity: npc_pid, player_eid: player_eid} do - Coordination.notify(npc_pid, {:movement_agent_updated, %Position{pos: %Coord{x: 19, y: 15}}, player_eid}) + test "update no target, entity too far to aggro", %{npc_entity: npc_pid, player_entity: player_pid} do + simulate_movement_update(player_pid, %Position{pos: %Coord{x: 19, y: 15}}, %Movement{goal: %Coord{x: 0, y: 0}}) assert {:ok, %Seek{target: nil}} = Entity.fetch_attribute(npc_pid, Seek) end @@ -42,33 +51,45 @@ defmodule Entice.Logic.SeekTest do #Set player as target Entity.put_attribute(npc_pid, %Seek{target: player_eid, aggro_distance: 10, escape_distance: 20}) - #Create unrelated entity - {:ok, unknown_eid, _} = Entity.start + #Create unrelated entity at a random pos and add it to map channel + {:ok, other_player_eid, other_player_pid} = Entity.start + simulate_movement_update(other_player_pid, %Position{pos: %Coord{x: 1, y: 2}}, %Movement{goal: %Coord{x: 3, y: 4}}) + Coordination.register(other_player_eid, TestMap) - Coordination.notify(npc_pid, {:movement_agent_updated, %Position{pos: %Coord{x: 11, y: 11}}, unknown_eid}) + #Move other player inside of aggro range and check that npc keeps initial target + simulate_movement_update(other_player_pid, %Position{pos: %Coord{x: 11, y: 11}}, %Movement{goal: %Coord{x: 0, y: 0}}) assert {:ok, %Seek{target: ^player_eid}} = Entity.fetch_attribute(npc_pid, Seek) end - test "update has target, entity is current target, entity escapes", %{npc_entity: npc_pid, player_eid: player_eid} do - init_pos = %Coord{x: 10, y: 10} - + test "update has target, entity is current target, entity escapes", + %{npc_entity: npc_pid, player_eid: player_eid, player_entity: player_pid, npc_init_pos: npc_init_pos} do #Set player as target Entity.put_attribute(npc_pid, %Seek{target: player_eid, aggro_distance: 10, escape_distance: 20}) - #Notify of new position outside escape range (dist = 20.6 > 20) - Coordination.notify(npc_pid, {:movement_agent_updated, %Position{pos: %Coord{x: 30, y: 15}}, player_eid}) + #Move target player far enough to escape + simulate_movement_update(player_pid, %Position{pos: %Coord{x: 30, y: 15}}, %Movement{goal: %Coord{x: 0, y: 0}}) assert {:ok, %Seek{target: nil}} = Entity.fetch_attribute(npc_pid, Seek) - assert {:ok, %Movement{goal: ^init_pos}} = Entity.fetch_attribute(npc_pid, Movement) + assert {:ok, %Movement{goal: ^npc_init_pos}} = Entity.fetch_attribute(npc_pid, Movement) end - test "update has target, entity is current target, entity does not escape", %{npc_entity: npc_pid, player_eid: player_eid} do + test "update has target, entity is current target, entity does not escape", %{npc_entity: npc_pid, player_eid: player_eid, player_entity: player_pid} do #Set player as target Entity.put_attribute(npc_pid, %Seek{target: player_eid, aggro_distance: 10, escape_distance: 20}) - #Notify of new position outside escape range (dist = 20.6 > 20) + #Move target player but not far enough to escape new_player_pos = %Coord{x: 14, y: 15} - Coordination.notify(npc_pid, {:movement_agent_updated, %Position{pos: new_player_pos}, player_eid}) + simulate_movement_update(player_pid, %Position{pos: new_player_pos}, %Movement{goal: %Coord{x: 0, y: 0}}) assert {:ok, %Seek{target: ^player_eid}} = Entity.fetch_attribute(npc_pid, Seek) assert {:ok, %Movement{goal: ^new_player_pos}} = Entity.fetch_attribute(npc_pid, Movement) end + + + defp simulate_movement_update(entity_pid, new_pos, new_movement) do + entity_pid |> Entity.attribute_transaction( + fn attrs -> + attrs + |> Map.put(Position, new_pos) + |> Map.put(Movement, new_movement) + end) + end end diff --git a/test/entice/logic/movement_test.exs b/test/entice/logic/movement_test.exs index b5ef656..c49418a 100644 --- a/test/entice/logic/movement_test.exs +++ b/test/entice/logic/movement_test.exs @@ -1,13 +1,10 @@ defmodule Entice.Logic.MovementTest do use ExUnit.Case, async: true alias Entice.Entity - alias Entice.Entity.Coordination alias Entice.Utils.Geom.Coord - alias Entice.Logic.{Movement, MapInstance} + alias Entice.Logic.Movement alias Entice.Logic.Player.Position - use Entice.Logic.Map - defmap TestMap1 setup do {:ok, _id, pid} = Entity.start @@ -41,16 +38,11 @@ defmodule Entice.Logic.MovementTest do test "update", %{entity: pid} do - Entity.put_attribute(pid, %MapInstance{map: TestMap1}) - Coordination.register_observer(self, TestMap1) - - new_pos = %Position{pos: %Coord{x: 42, y: 1337}, plane: 7} Movement.update(pid, - new_pos, + %Position{pos: %Coord{x: 42, y: 1337}, plane: 7}, %Movement{goal: %Coord{x: 1337, y: 42}, plane: 13, move_type: 5, velocity: 0.5}) assert {:ok, %Position{plane: 7}} = Entity.fetch_attribute(pid, Position) - assert {:ok, %Movement{move_type: 5}} = Entity.fetch_attribute(pid, Movement) - assert_receive {:movement_agent_updated, ^new_pos, ^pid} #TODO: update to use eid + assert {:ok, %Movement{move_type: 5}} = Entity.fetch_attribute(pid, Movement) end From 239f2cceae908d5ce6e616beece6373096fd4949 Mon Sep 17 00:00:00 2001 From: EtienneDesticourt Date: Wed, 10 Aug 2016 17:31:57 +0200 Subject: [PATCH 06/20] :pos -> :coord --- lib/entice/logic/ai/seek.ex | 16 ++++++------ lib/entice/logic/movement.ex | 4 +-- lib/entice/logic/npc.ex | 4 +-- lib/entice/logic/player.ex | 5 ++-- test/entice/logic/ai/seek_test.exs | 38 ++++++++++++++--------------- test/entice/logic/movement_test.exs | 4 +-- 6 files changed, 35 insertions(+), 36 deletions(-) diff --git a/lib/entice/logic/ai/seek.ex b/lib/entice/logic/ai/seek.ex index b9a0d72..f85affb 100644 --- a/lib/entice/logic/ai/seek.ex +++ b/lib/entice/logic/ai/seek.ex @@ -29,28 +29,28 @@ defmodule Entice.Logic.Seek do def handle_event({:entity_change, %{entity_id: eid}}, %Entity{id: eid} = entity), do: {:ok, entity} - def handle_event({:entity_change, %{changed: %{Position => %Position{pos: mover_pos}}, entity_id: moving_entity_id}}, - %Entity{attributes: %{Position => %Position{pos: my_pos}, + def handle_event({:entity_change, %{changed: %{Position => %Position{coord: mover_coord}}, entity_id: moving_entity_id}}, + %Entity{attributes: %{Position => %Position{coord: my_coord}, Movement => _, - Npc => %Npc{init_pos: init_pos}, + Npc => %Npc{init_coord: init_coord}, Seek => %Seek{aggro_distance: aggro_distance, escape_distance: escape_distance, target: target}}} = entity) do case target do nil -> - if calc_distance(my_pos, mover_pos) < aggro_distance do + if calc_distance(my_coord, mover_coord) < aggro_distance do {:ok, entity |> update_attribute(Seek, fn(s) -> %Seek{s | target: moving_entity_id} end) - |> update_attribute(Movement, fn(m) -> %Movement{m | goal: mover_pos} end)} + |> update_attribute(Movement, fn(m) -> %Movement{m | goal: mover_coord} end)} else {:ok, entity} end ^moving_entity_id -> - if calc_distance(init_pos, mover_pos) >= escape_distance do + if calc_distance(init_coord, mover_coord) >= escape_distance do {:ok, entity |> update_attribute(Seek, fn(s) -> %Seek{s | target: nil} end) - |> update_attribute(Movement, fn(m) -> %Movement{m | goal: init_pos} end)} + |> update_attribute(Movement, fn(m) -> %Movement{m | goal: init_coord} end)} else {:ok, entity - |> update_attribute(Movement, fn(m) -> %Movement{m | goal: mover_pos} end)} + |> update_attribute(Movement, fn(m) -> %Movement{m | goal: mover_coord} end)} end _ -> {:ok, entity} diff --git a/lib/entice/logic/movement.ex b/lib/entice/logic/movement.ex index c1d4430..e7e9e0c 100644 --- a/lib/entice/logic/movement.ex +++ b/lib/entice/logic/movement.ex @@ -37,8 +37,8 @@ defmodule Entice.Logic.Movement do def init(%Entity{attributes: %{Movement => _}} = entity, _args), do: {:ok, entity} - def init(%Entity{attributes: %{Position => %Position{pos: pos, plane: plane}}} = entity, _args), - do: {:ok, entity |> put_attribute(%Movement{goal: pos, plane: plane})} + def init(%Entity{attributes: %{Position => %Position{coord: coord, plane: plane}}} = entity, _args), + do: {:ok, entity |> put_attribute(%Movement{goal: coord, plane: plane})} def init(entity, _args), do: {:ok, entity |> put_attribute(%Movement{})} diff --git a/lib/entice/logic/npc.ex b/lib/entice/logic/npc.ex index 9b86de8..f193fd7 100644 --- a/lib/entice/logic/npc.ex +++ b/lib/entice/logic/npc.ex @@ -4,7 +4,7 @@ defmodule Entice.Logic.Npc do alias Entice.Logic.{Npc, Vitals, Movement, Seek} alias Entice.Logic.Player.{Name, Position, Level} - defstruct npc_model_id: :dhuum, init_pos: %Position{} + defstruct npc_model_id: :dhuum, init_coord: %Position{} def spawn(name, model, %Position{} = position, %{seeks: seeks} \\ %{seeks: true}) @@ -24,7 +24,7 @@ defmodule Entice.Logic.Npc do attrs |> Map.put(Name, %Name{name: name}) |> Map.put(Position, position) - |> Map.put(Npc, %Npc{npc_model_id: model, init_pos: position.pos}) + |> Map.put(Npc, %Npc{npc_model_id: model, init_coord: position.coord}) |> Map.put(Level, %Level{level: 20}) end) end diff --git a/lib/entice/logic/player.ex b/lib/entice/logic/player.ex index ca45d16..3d7b765 100644 --- a/lib/entice/logic/player.ex +++ b/lib/entice/logic/player.ex @@ -9,9 +9,8 @@ defmodule Entice.Logic.Player do defmodule Name, do: defstruct( name: "Unknown Entity") - #TODO: rename :pos to :coord ? Confusing sometimes defmodule Position, do: defstruct( - pos: %Coord{}, + coord: %Coord{}, plane: 1) defmodule Appearance, do: defstruct( @@ -32,7 +31,7 @@ defmodule Entice.Logic.Player do entity |> Entity.attribute_transaction(fn (attrs) -> attrs |> Map.put(Name, %Name{name: name}) - |> Map.put(Position, %Position{pos: map.spawn}) + |> Map.put(Position, %Position{coord: map.spawn}) |> Map.put(Appearance, appearance) |> Map.put(Level, %Level{level: 20}) end) diff --git a/test/entice/logic/ai/seek_test.exs b/test/entice/logic/ai/seek_test.exs index 487771b..48d2bcd 100644 --- a/test/entice/logic/ai/seek_test.exs +++ b/test/entice/logic/ai/seek_test.exs @@ -11,16 +11,16 @@ defmodule Entice.Logic.SeekTest do defmap TestMap setup do - npc_init_pos = %Coord{x: 10, y: 10} - {:ok, npc_eid, npc_pid} = Npc.spawn("Dhuum", :dhuum, %Position{pos: npc_init_pos}) + npc_init_coord = %Coord{x: 10, y: 10} + {:ok, npc_eid, npc_pid} = Npc.spawn("Dhuum", :dhuum, %Position{coord: npc_init_coord}) {:ok, player_eid, player_pid} = Entity.start #Sets an initial pos for the player so the Position attr appears as changed and not added in the following tests - simulate_movement_update(player_pid, %Position{pos: %Coord{x: 1, y: 2}}, %Movement{goal: %Coord{x: 3, y: 4}}) + simulate_movement_update(player_pid, %Position{coord: %Coord{x: 1, y: 2}}, %Movement{goal: %Coord{x: 3, y: 4}}) Player.register(player_pid, TestMap) Coordination.register(player_eid, TestMap) Coordination.register(npc_eid, TestMap) - {:ok, [npc_entity: npc_pid, npc_eid: npc_eid, player_eid: player_eid, player_entity: player_pid, npc_init_pos: npc_init_pos]} + {:ok, [npc_entity: npc_pid, npc_eid: npc_eid, player_eid: player_eid, player_entity: player_pid, npc_init_coord: npc_init_coord]} end test "correct default register", %{npc_entity: pid} do @@ -28,22 +28,22 @@ defmodule Entice.Logic.SeekTest do end test "update same entity", %{npc_entity: pid} do - simulate_movement_update(pid, %Position{pos: %Coord{x: 1, y: 2}}, %Movement{goal: %Coord{x: 3, y: 4}}) + simulate_movement_update(pid, %Position{coord: %Coord{x: 1, y: 2}}, %Movement{goal: %Coord{x: 3, y: 4}}) assert {:ok, %Seek{target: nil}} = Entity.fetch_attribute(pid, Seek) end test "update no target, entity close enough to aggro", %{npc_entity: npc_pid, player_eid: player_eid, player_entity: player_pid} do #Move player within 2 units of distance of npc with aggro distance of 10 (default) - mover_pos = %Coord{x: 14, y: 10} - simulate_movement_update(player_pid, %Position{pos: mover_pos}, %Movement{goal: mover_pos}) + mover_coord = %Coord{x: 14, y: 10} + simulate_movement_update(player_pid, %Position{coord: mover_coord}, %Movement{goal: mover_coord}) assert {:ok, %Seek{target: ^player_eid}} = Entity.fetch_attribute(npc_pid, Seek) - assert {:ok, %Movement{goal: ^mover_pos}} = Entity.fetch_attribute(npc_pid, Movement) + assert {:ok, %Movement{goal: ^mover_coord}} = Entity.fetch_attribute(npc_pid, Movement) end test "update no target, entity too far to aggro", %{npc_entity: npc_pid, player_entity: player_pid} do - simulate_movement_update(player_pid, %Position{pos: %Coord{x: 19, y: 15}}, %Movement{goal: %Coord{x: 0, y: 0}}) + simulate_movement_update(player_pid, %Position{coord: %Coord{x: 19, y: 15}}, %Movement{goal: %Coord{x: 0, y: 0}}) assert {:ok, %Seek{target: nil}} = Entity.fetch_attribute(npc_pid, Seek) end @@ -53,23 +53,23 @@ defmodule Entice.Logic.SeekTest do #Create unrelated entity at a random pos and add it to map channel {:ok, other_player_eid, other_player_pid} = Entity.start - simulate_movement_update(other_player_pid, %Position{pos: %Coord{x: 1, y: 2}}, %Movement{goal: %Coord{x: 3, y: 4}}) + simulate_movement_update(other_player_pid, %Position{coord: %Coord{x: 1, y: 2}}, %Movement{goal: %Coord{x: 3, y: 4}}) Coordination.register(other_player_eid, TestMap) #Move other player inside of aggro range and check that npc keeps initial target - simulate_movement_update(other_player_pid, %Position{pos: %Coord{x: 11, y: 11}}, %Movement{goal: %Coord{x: 0, y: 0}}) + simulate_movement_update(other_player_pid, %Position{coord: %Coord{x: 11, y: 11}}, %Movement{goal: %Coord{x: 0, y: 0}}) assert {:ok, %Seek{target: ^player_eid}} = Entity.fetch_attribute(npc_pid, Seek) end test "update has target, entity is current target, entity escapes", - %{npc_entity: npc_pid, player_eid: player_eid, player_entity: player_pid, npc_init_pos: npc_init_pos} do + %{npc_entity: npc_pid, player_eid: player_eid, player_entity: player_pid, npc_init_coord: npc_init_coord} do #Set player as target Entity.put_attribute(npc_pid, %Seek{target: player_eid, aggro_distance: 10, escape_distance: 20}) #Move target player far enough to escape - simulate_movement_update(player_pid, %Position{pos: %Coord{x: 30, y: 15}}, %Movement{goal: %Coord{x: 0, y: 0}}) + simulate_movement_update(player_pid, %Position{coord: %Coord{x: 30, y: 15}}, %Movement{goal: %Coord{x: 0, y: 0}}) assert {:ok, %Seek{target: nil}} = Entity.fetch_attribute(npc_pid, Seek) - assert {:ok, %Movement{goal: ^npc_init_pos}} = Entity.fetch_attribute(npc_pid, Movement) + assert {:ok, %Movement{goal: ^npc_init_coord}} = Entity.fetch_attribute(npc_pid, Movement) end test "update has target, entity is current target, entity does not escape", %{npc_entity: npc_pid, player_eid: player_eid, player_entity: player_pid} do @@ -77,18 +77,18 @@ defmodule Entice.Logic.SeekTest do Entity.put_attribute(npc_pid, %Seek{target: player_eid, aggro_distance: 10, escape_distance: 20}) #Move target player but not far enough to escape - new_player_pos = %Coord{x: 14, y: 15} - simulate_movement_update(player_pid, %Position{pos: new_player_pos}, %Movement{goal: %Coord{x: 0, y: 0}}) + new_player_coord = %Coord{x: 14, y: 15} + simulate_movement_update(player_pid, %Position{coord: new_player_coord}, %Movement{goal: %Coord{x: 0, y: 0}}) assert {:ok, %Seek{target: ^player_eid}} = Entity.fetch_attribute(npc_pid, Seek) - assert {:ok, %Movement{goal: ^new_player_pos}} = Entity.fetch_attribute(npc_pid, Movement) + assert {:ok, %Movement{goal: ^new_player_coord}} = Entity.fetch_attribute(npc_pid, Movement) end - defp simulate_movement_update(entity_pid, new_pos, new_movement) do + defp simulate_movement_update(entity_pid, new_coord, new_movement) do entity_pid |> Entity.attribute_transaction( fn attrs -> attrs - |> Map.put(Position, new_pos) + |> Map.put(Position, new_coord) |> Map.put(Movement, new_movement) end) end diff --git a/test/entice/logic/movement_test.exs b/test/entice/logic/movement_test.exs index c49418a..70be2ec 100644 --- a/test/entice/logic/movement_test.exs +++ b/test/entice/logic/movement_test.exs @@ -21,7 +21,7 @@ defmodule Entice.Logic.MovementTest do test "register with position", %{entity: pid} do Movement.unregister(pid) # remove again, so we can add a new one - Entity.put_attribute(pid, %Position{pos: %Coord{x: 42, y: 1337}, plane: 7}) + Entity.put_attribute(pid, %Position{coord: %Coord{x: 42, y: 1337}, plane: 7}) Movement.register(pid) m = %Movement{goal: %Coord{x: 42, y: 1337}, plane: 7} assert {:ok, ^m} = Entity.fetch_attribute(pid, Movement) @@ -39,7 +39,7 @@ defmodule Entice.Logic.MovementTest do test "update", %{entity: pid} do Movement.update(pid, - %Position{pos: %Coord{x: 42, y: 1337}, plane: 7}, + %Position{coord: %Coord{x: 42, y: 1337}, plane: 7}, %Movement{goal: %Coord{x: 1337, y: 42}, plane: 13, move_type: 5, velocity: 0.5}) assert {:ok, %Position{plane: 7}} = Entity.fetch_attribute(pid, Position) assert {:ok, %Movement{move_type: 5}} = Entity.fetch_attribute(pid, Movement) From 165397ce0bbf22692e98dc41c9d75b6a50180ae8 Mon Sep 17 00:00:00 2001 From: EtienneDesticourt Date: Wed, 10 Aug 2016 17:35:36 +0200 Subject: [PATCH 07/20] Modified way AI behaviours are chosen for an npc --- lib/entice/logic/npc.ex | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/entice/logic/npc.ex b/lib/entice/logic/npc.ex index f193fd7..91f6984 100644 --- a/lib/entice/logic/npc.ex +++ b/lib/entice/logic/npc.ex @@ -7,13 +7,13 @@ defmodule Entice.Logic.Npc do defstruct npc_model_id: :dhuum, init_coord: %Position{} - def spawn(name, model, %Position{} = position, %{seeks: seeks} \\ %{seeks: true}) + def spawn(name, model, %Position{} = position, opts \\ []) when is_binary(name) and is_atom(model) do {:ok, id, pid} = Entity.start() Npc.register(id, name, model, position) Vitals.register(id) Movement.register(id) - if seeks, do: Seek.register(id) + if opts[:seeks] || true, do: Seek.register(id) {:ok, id, pid} end From f3825069c7afa0bc02c6a90dea7bdf0c33cae4c6 Mon Sep 17 00:00:00 2001 From: EtienneDesticourt Date: Wed, 10 Aug 2016 17:39:23 +0200 Subject: [PATCH 08/20] Removed added whitespace --- lib/entice/logic/map_instance.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/entice/logic/map_instance.ex b/lib/entice/logic/map_instance.ex index cbcf047..588310c 100644 --- a/lib/entice/logic/map_instance.ex +++ b/lib/entice/logic/map_instance.ex @@ -51,7 +51,7 @@ defmodule Entice.Logic.MapInstance do def handle_event( {:map_instance_npc_add, %{name: name, model: model, position: position}}, %Entity{attributes: %{MapInstance => %MapInstance{map: map}}} = entity) do - {:ok, eid, _pid} = Npc.spawn(name, model, position) + {:ok, eid, _pid} = Npc.spawn(name, model, position) Coordination.register(eid, map) # TODO change map to something else if we have multiple instances {:ok, entity} end From 6fdee5b934ed6cd617850b712e4b455dcebcdfea Mon Sep 17 00:00:00 2001 From: EtienneDesticourt Date: Wed, 10 Aug 2016 17:42:46 +0200 Subject: [PATCH 09/20] :pos -> :coord --- test/entice/logic/npc_test.exs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/entice/logic/npc_test.exs b/test/entice/logic/npc_test.exs index abaf792..33284e0 100644 --- a/test/entice/logic/npc_test.exs +++ b/test/entice/logic/npc_test.exs @@ -8,7 +8,7 @@ defmodule Entice.Logic.NpcTest do setup do - {:ok, _id, pid} = Npc.spawn("Dhuum", :dhuum, %Position{pos: %Coord{x: 1, y: 2}, plane: 3}) + {:ok, _id, pid} = Npc.spawn("Dhuum", :dhuum, %Position{coord: %Coord{x: 1, y: 2}, plane: 3}) {:ok, [entity: pid]} end @@ -16,7 +16,7 @@ defmodule Entice.Logic.NpcTest do assert {:ok, %Name{name: "Dhuum"}} = Entity.fetch_attribute(pid, Name) assert {:ok, %Npc{npc_model_id: :dhuum}} = Entity.fetch_attribute(pid, Npc) assert {:ok, %Level{level: 20}} = Entity.fetch_attribute(pid, Level) - assert {:ok, %Position{pos: %Coord{x: 1, y: 2}, plane: 3}} = Entity.fetch_attribute(pid, Position) + assert {:ok, %Position{coord: %Coord{x: 1, y: 2}, plane: 3}} = Entity.fetch_attribute(pid, Position) end test "correct unregister", %{entity: pid} do From f15a24fe18346c40e5f346d474095b01b2d025c7 Mon Sep 17 00:00:00 2001 From: EtienneDesticourt Date: Thu, 11 Aug 2016 18:55:48 +0200 Subject: [PATCH 10/20] Whitespace removal --- lib/entice/logic/ai/seek.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/entice/logic/ai/seek.ex b/lib/entice/logic/ai/seek.ex index f85affb..5635273 100644 --- a/lib/entice/logic/ai/seek.ex +++ b/lib/entice/logic/ai/seek.ex @@ -22,7 +22,7 @@ defmodule Entice.Logic.Seek do def init(entity, %{aggro_distance: aggro_distance, escape_distance: escape_distance}), do: {:ok, entity |> put_attribute(%Seek{aggro_distance: aggro_distance, escape_distance: escape_distance})} - def init(entity, _args), + def init(entity, _args), do: {:ok, entity |> put_attribute(%Seek{})} #No introspection for npcs ;) From b2a22528590bdf38a81e50efbb787ce35c1cc5f9 Mon Sep 17 00:00:00 2001 From: EtienneDesticourt Date: Fri, 12 Aug 2016 01:41:47 +0200 Subject: [PATCH 11/20] Added auto pos update skeleton --- lib/entice/logic/movement.ex | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/lib/entice/logic/movement.ex b/lib/entice/logic/movement.ex index e7e9e0c..3a78b76 100644 --- a/lib/entice/logic/movement.ex +++ b/lib/entice/logic/movement.ex @@ -8,7 +8,7 @@ defmodule Entice.Logic.Movement do Note that velocity is actually a coefficient for the real velocity thats used inside the client, but for simplicities sake we used velocity as a name. """ - defstruct goal: %Coord{}, plane: 1, move_type: 9, velocity: 1.0 + defstruct goal: %Coord{}, plane: 1, move_type: 9, velocity: 1.0, update_self: false, update_delay: 1 def register(entity), @@ -43,8 +43,22 @@ defmodule Entice.Logic.Movement do def init(entity, _args), do: {:ok, entity |> put_attribute(%Movement{})} + def handle_event({:movement_calculate_next}, + %Entity{attributes: %{Movement => %Movement{update_self: update_self, update_delay: update_delay}}} = _entity) do + #TODO: implement once the whole collision business is handled + if update_self, do: start_update_trigger_timer(:movement_calculate_next, update_delay) + end def terminate(_reason, entity), do: {:ok, entity |> remove_attribute(Movement)} + + defp start_update_trigger_timer(message, time) do + if time == 0 do + self |> send(message) + nil + else + self |> Process.send_after(message, time) + end + end end end From 12af7b60c9d1f1ee0bb6927edc8cc12647b894b8 Mon Sep 17 00:00:00 2001 From: EtienneDesticourt Date: Mon, 15 Aug 2016 16:23:21 +0200 Subject: [PATCH 12/20] Cleaned up seeking handler, fixed movement update handler --- lib/entice/logic/ai/seek.ex | 41 ++++++++++++++++++++++++------------ lib/entice/logic/movement.ex | 14 +++--------- 2 files changed, 31 insertions(+), 24 deletions(-) diff --git a/lib/entice/logic/ai/seek.ex b/lib/entice/logic/ai/seek.ex index 5635273..0b4d059 100644 --- a/lib/entice/logic/ai/seek.ex +++ b/lib/entice/logic/ai/seek.ex @@ -29,37 +29,52 @@ defmodule Entice.Logic.Seek do def handle_event({:entity_change, %{entity_id: eid}}, %Entity{id: eid} = entity), do: {:ok, entity} - def handle_event({:entity_change, %{changed: %{Position => %Position{coord: mover_coord}}, entity_id: moving_entity_id}}, + def handle_event({:entity_change, %{changed: %{Position => %Position{coord: mover_coord}}, entity_id: moving_entity_id}}, %Entity{attributes: %{Position => %Position{coord: my_coord}, Movement => _, Npc => %Npc{init_coord: init_coord}, Seek => %Seek{aggro_distance: aggro_distance, escape_distance: escape_distance, target: target}}} = entity) do case target do nil -> - if calc_distance(my_coord, mover_coord) < aggro_distance do - {:ok, entity |> update_attribute(Seek, fn(s) -> %Seek{s | target: moving_entity_id} end) - |> update_attribute(Movement, fn(m) -> %Movement{m | goal: mover_coord} end)} + if in_aggro_range?(my_coord, mover_coord, aggro_distance) do + {:ok, entity |> seek_target_current_coord(moving_entity_id, mover_coord)} else {:ok, entity} - end - + end + + ^moving_entity_id -> - if calc_distance(init_coord, mover_coord) >= escape_distance do - {:ok, entity - |> update_attribute(Seek, fn(s) -> %Seek{s | target: nil} end) - |> update_attribute(Movement, fn(m) -> %Movement{m | goal: init_coord} end)} + if past_escape_range?(init_coord, mover_coord, escape_distance) do + {:ok, entity |> return_to_spawn(init_coord)} else - {:ok, entity - |> update_attribute(Movement, fn(m) -> %Movement{m | goal: mover_coord} end)} + {:ok, entity |> seek_target_current_coord(moving_entity_id, mover_coord)} end _ -> {:ok, entity} - end + end end def terminate(_reason, entity), do: {:ok, entity |> remove_attribute(Seek)} + defp in_aggro_range?(my_coord, mover_coord, aggro_distance), + do: calc_distance(my_coord, mover_coord) < aggro_distance + + defp past_escape_range?(init_coord, mover_coord, escape_distance), + do: calc_distance(init_coord, mover_coord) >= escape_distance + + defp seek_target_current_coord(entity, target_id, target_coord) do + entity + |> update_attribute(Seek, fn(s) -> %Seek{s | target: target_id} end) + |> update_attribute(Movement, fn(m) -> %Movement{m | goal: target_coord} end) + end + + defp return_to_spawn(entity, spawn_coord) do + entity + |> update_attribute(Seek, fn(s) -> %Seek{s | target: nil} end) + |> update_attribute(Movement, fn(m) -> %Movement{m | goal: spawn_coord} end) + end + #TODO: Should probably move to Coord in Utils defp calc_distance(%Coord{x: x1, y: y1}, %Coord{x: x2, y: y2}) do :math.sqrt(:math.pow((x2-x1), 2) + :math.pow((y2-y1), 2)) diff --git a/lib/entice/logic/movement.ex b/lib/entice/logic/movement.ex index 3a78b76..5d0efde 100644 --- a/lib/entice/logic/movement.ex +++ b/lib/entice/logic/movement.ex @@ -44,21 +44,13 @@ defmodule Entice.Logic.Movement do do: {:ok, entity |> put_attribute(%Movement{})} def handle_event({:movement_calculate_next}, - %Entity{attributes: %{Movement => %Movement{update_self: update_self, update_delay: update_delay}}} = _entity) do + %Entity{attributes: %{Movement => %Movement{update_self: update_self, update_delay: update_delay}}} = entity) do #TODO: implement once the whole collision business is handled - if update_self, do: start_update_trigger_timer(:movement_calculate_next, update_delay) + if update_self, do: self |> Process.send_after(:movement_calculate_next, update_delay) + {:ok, entity} end def terminate(_reason, entity), do: {:ok, entity |> remove_attribute(Movement)} - - defp start_update_trigger_timer(message, time) do - if time == 0 do - self |> send(message) - nil - else - self |> Process.send_after(message, time) - end - end end end From 260666fcbadf4a9d9ca1d4fbaca6cfcc766b8a67 Mon Sep 17 00:00:00 2001 From: EtienneDesticourt Date: Mon, 15 Aug 2016 16:56:21 +0200 Subject: [PATCH 13/20] Removed excess linebreak --- lib/entice/logic/ai/seek.ex | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/entice/logic/ai/seek.ex b/lib/entice/logic/ai/seek.ex index 0b4d059..7ea878d 100644 --- a/lib/entice/logic/ai/seek.ex +++ b/lib/entice/logic/ai/seek.ex @@ -42,7 +42,6 @@ defmodule Entice.Logic.Seek do {:ok, entity} end - ^moving_entity_id -> if past_escape_range?(init_coord, mover_coord, escape_distance) do {:ok, entity |> return_to_spawn(init_coord)} From f718ddf87e3495a2c4d373d897e7ae04a59f84ac Mon Sep 17 00:00:00 2001 From: EtienneDesticourt Date: Tue, 16 Aug 2016 15:41:08 +0200 Subject: [PATCH 14/20] Naming changes --- lib/entice/logic/movement.ex | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/lib/entice/logic/movement.ex b/lib/entice/logic/movement.ex index 5d0efde..66b957a 100644 --- a/lib/entice/logic/movement.ex +++ b/lib/entice/logic/movement.ex @@ -4,11 +4,14 @@ defmodule Entice.Logic.Movement do alias Entice.Logic.{Movement, Player.Position} + @update_interval 50 + + @doc """ Note that velocity is actually a coefficient for the real velocity thats used inside the client, but for simplicities sake we used velocity as a name. """ - defstruct goal: %Coord{}, plane: 1, move_type: 9, velocity: 1.0, update_self: false, update_delay: 1 + defstruct goal: %Coord{}, plane: 1, move_type: 9, velocity: 1.0, auto_updating?: false def register(entity), @@ -30,6 +33,8 @@ defmodule Entice.Logic.Movement do end) end + def update_interval, do: @update_interval + defmodule Behaviour do use Entice.Entity.Behaviour @@ -44,9 +49,9 @@ defmodule Entice.Logic.Movement do do: {:ok, entity |> put_attribute(%Movement{})} def handle_event({:movement_calculate_next}, - %Entity{attributes: %{Movement => %Movement{update_self: update_self, update_delay: update_delay}}} = entity) do + %Entity{attributes: %{Movement => %Movement{auto_updating?: auto_updating?}}} = entity) do #TODO: implement once the whole collision business is handled - if update_self, do: self |> Process.send_after(:movement_calculate_next, update_delay) + if auto_updating?, do: self |> Process.send_after(:movement_calculate_next, Movement.update_interval) {:ok, entity} end From 38b65ca11018058e593022c332346fd57ad4811b Mon Sep 17 00:00:00 2001 From: EtienneDesticourt Date: Tue, 11 Oct 2016 00:22:03 +0200 Subject: [PATCH 15/20] Updated deps --- mix.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/mix.lock b/mix.lock index 68c0655..bcfa7b8 100644 --- a/mix.lock +++ b/mix.lock @@ -1,5 +1,5 @@ %{"entice_entity": {:git, "https://github.com/entice/entity.git", "c26f6f77ae650e25e6cd2ffea8aae46b7d83966a", [ref: "c26f6f77ae650e25e6cd2ffea8aae46b7d83966a"]}, "entice_utils": {:git, "https://github.com/entice/utils.git", "79ead4dca77324b4c24f584468edbaff2029eeab", [ref: "79ead4dca77324b4c24f584468edbaff2029eeab"]}, - "inflex": {:hex, :inflex, "1.5.0"}, - "pipe": {:hex, :pipe, "0.0.2"}, - "uuid": {:hex, :uuid, "1.1.3"}} + "inflex": {:hex, :inflex, "1.5.0", "e4ff5d900280b2011b24d1ac1c4590986ee5add2ea644c9894e72213cf93ff0b", [:mix], []}, + "pipe": {:hex, :pipe, "0.0.2", "eff98a868b426745acef103081581093ff5c1b88100f8ff5949b4a30e81d0d9f", [:mix], []}, + "uuid": {:hex, :uuid, "1.1.3", "06ca38801a1a95b751701ca40716bb97ddf76dfe7e26da0eec7dba636740d57a", [:mix], []}} From f819c1dbb75c37936ee7ef337ad3e28ebcfb48fd Mon Sep 17 00:00:00 2001 From: EtienneDesticourt Date: Tue, 11 Oct 2016 00:22:32 +0200 Subject: [PATCH 16/20] Put better default aggro/escape dist --- lib/entice/logic/ai/seek.ex | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/entice/logic/ai/seek.ex b/lib/entice/logic/ai/seek.ex index 7ea878d..b13242f 100644 --- a/lib/entice/logic/ai/seek.ex +++ b/lib/entice/logic/ai/seek.ex @@ -3,7 +3,7 @@ defmodule Entice.Logic.Seek do alias Entice.Logic.{Seek, Player.Position, Npc, Movement} alias Entice.Utils.Geom.Coord - defstruct target: nil, aggro_distance: 10, escape_distance: 20 + defstruct target: nil, aggro_distance: 1000, escape_distance: 2000 def register(entity), do: Entity.put_behaviour(entity, Seek.Behaviour, []) @@ -57,7 +57,7 @@ defmodule Entice.Logic.Seek do do: {:ok, entity |> remove_attribute(Seek)} defp in_aggro_range?(my_coord, mover_coord, aggro_distance), - do: calc_distance(my_coord, mover_coord) < aggro_distance + do: calc_distance(my_coord, mover_coord) <= aggro_distance defp past_escape_range?(init_coord, mover_coord, escape_distance), do: calc_distance(init_coord, mover_coord) >= escape_distance From f7ab01942514720b45b11bd3fb7f0ddbb9bb513f Mon Sep 17 00:00:00 2001 From: EtienneDesticourt Date: Tue, 11 Oct 2016 00:23:25 +0200 Subject: [PATCH 17/20] Set seeking as default npc behaviour, started auto updates at behaviour initialization --- lib/entice/logic/map_instance.ex | 2 +- lib/entice/logic/movement.ex | 9 ++++++++- lib/entice/logic/npc.ex | 8 ++++++-- 3 files changed, 15 insertions(+), 4 deletions(-) diff --git a/lib/entice/logic/map_instance.ex b/lib/entice/logic/map_instance.ex index 588310c..e286e3d 100644 --- a/lib/entice/logic/map_instance.ex +++ b/lib/entice/logic/map_instance.ex @@ -51,7 +51,7 @@ defmodule Entice.Logic.MapInstance do def handle_event( {:map_instance_npc_add, %{name: name, model: model, position: position}}, %Entity{attributes: %{MapInstance => %MapInstance{map: map}}} = entity) do - {:ok, eid, _pid} = Npc.spawn(name, model, position) + {:ok, eid, _pid} = Npc.spawn(name, model, position, [:seeks]) Coordination.register(eid, map) # TODO change map to something else if we have multiple instances {:ok, entity} end diff --git a/lib/entice/logic/movement.ex b/lib/entice/logic/movement.ex index 66b957a..cd7ffdc 100644 --- a/lib/entice/logic/movement.ex +++ b/lib/entice/logic/movement.ex @@ -17,6 +17,8 @@ defmodule Entice.Logic.Movement do def register(entity), do: Entity.put_behaviour(entity, Movement.Behaviour, []) + def register(entity, %{auto_updating?: auto_updating?}), + do: Entity.put_behaviour(entity, Movement.Behaviour, %{auto_updating?: auto_updating?}) def unregister(entity), do: Entity.remove_behaviour(entity, Movement.Behaviour) @@ -42,6 +44,11 @@ defmodule Entice.Logic.Movement do def init(%Entity{attributes: %{Movement => _}} = entity, _args), do: {:ok, entity} + def init(%Entity{attributes: %{Position => %Position{coord: coord, plane: plane}}} = entity, %{auto_updating?: true}) do + self |> Process.send_after({:movement_calculate_next}, 1) + {:ok, entity |> put_attribute(%Movement{goal: coord, plane: plane, auto_updating?: true})} + end + def init(%Entity{attributes: %{Position => %Position{coord: coord, plane: plane}}} = entity, _args), do: {:ok, entity |> put_attribute(%Movement{goal: coord, plane: plane})} @@ -51,7 +58,7 @@ defmodule Entice.Logic.Movement do def handle_event({:movement_calculate_next}, %Entity{attributes: %{Movement => %Movement{auto_updating?: auto_updating?}}} = entity) do #TODO: implement once the whole collision business is handled - if auto_updating?, do: self |> Process.send_after(:movement_calculate_next, Movement.update_interval) + if auto_updating?, do: self |> Process.send_after({:movement_calculate_next}, Movement.update_interval) {:ok, entity} end diff --git a/lib/entice/logic/npc.ex b/lib/entice/logic/npc.ex index 91f6984..23e719e 100644 --- a/lib/entice/logic/npc.ex +++ b/lib/entice/logic/npc.ex @@ -12,8 +12,12 @@ defmodule Entice.Logic.Npc do {:ok, id, pid} = Entity.start() Npc.register(id, name, model, position) Vitals.register(id) - Movement.register(id) - if opts[:seeks] || true, do: Seek.register(id) + if opts[:seeks] || true do + Seek.register(id) + Movement.register(id, %{auto_updating?: true}) + else + Movement.register(id) + end {:ok, id, pid} end From 0c18581aabb6e3f9046bf8ad6c00f777268c67ac Mon Sep 17 00:00:00 2001 From: EtienneDesticourt Date: Tue, 11 Oct 2016 00:45:14 +0200 Subject: [PATCH 18/20] Fixed tests Should probably write smarter tests. --- test/entice/logic/ai/seek_test.exs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/test/entice/logic/ai/seek_test.exs b/test/entice/logic/ai/seek_test.exs index 48d2bcd..8dc3060 100644 --- a/test/entice/logic/ai/seek_test.exs +++ b/test/entice/logic/ai/seek_test.exs @@ -23,8 +23,8 @@ defmodule Entice.Logic.SeekTest do {:ok, [npc_entity: npc_pid, npc_eid: npc_eid, player_eid: player_eid, player_entity: player_pid, npc_init_coord: npc_init_coord]} end - test "correct default register", %{npc_entity: pid} do - assert {:ok, %Seek{target: nil, aggro_distance: 10, escape_distance: 20}} = Entity.fetch_attribute(pid, Seek) + test "register", %{npc_entity: pid} do + assert {:ok, %Seek{target: nil, aggro_distance: _, escape_distance: _}} = Entity.fetch_attribute(pid, Seek) end test "update same entity", %{npc_entity: pid} do @@ -33,7 +33,7 @@ defmodule Entice.Logic.SeekTest do end test "update no target, entity close enough to aggro", %{npc_entity: npc_pid, player_eid: player_eid, player_entity: player_pid} do - #Move player within 2 units of distance of npc with aggro distance of 10 (default) + #Move player within 2 units of distance of npc with aggro distance of 1000 mover_coord = %Coord{x: 14, y: 10} simulate_movement_update(player_pid, %Position{coord: mover_coord}, %Movement{goal: mover_coord}) @@ -43,7 +43,7 @@ defmodule Entice.Logic.SeekTest do end test "update no target, entity too far to aggro", %{npc_entity: npc_pid, player_entity: player_pid} do - simulate_movement_update(player_pid, %Position{coord: %Coord{x: 19, y: 15}}, %Movement{goal: %Coord{x: 0, y: 0}}) + simulate_movement_update(player_pid, %Position{coord: %Coord{x: 460, y: 910}}, %Movement{goal: %Coord{x: 0, y: 0}}) assert {:ok, %Seek{target: nil}} = Entity.fetch_attribute(npc_pid, Seek) end @@ -61,7 +61,7 @@ defmodule Entice.Logic.SeekTest do assert {:ok, %Seek{target: ^player_eid}} = Entity.fetch_attribute(npc_pid, Seek) end - test "update has target, entity is current target, entity escapes", + test "update has target, entity is current target, entity escapes", %{npc_entity: npc_pid, player_eid: player_eid, player_entity: player_pid, npc_init_coord: npc_init_coord} do #Set player as target Entity.put_attribute(npc_pid, %Seek{target: player_eid, aggro_distance: 10, escape_distance: 20}) @@ -90,6 +90,6 @@ defmodule Entice.Logic.SeekTest do attrs |> Map.put(Position, new_coord) |> Map.put(Movement, new_movement) - end) + end) end end From d93eeb8e3488405386a659c61e2d933f60ff07ed Mon Sep 17 00:00:00 2001 From: EtienneDesticourt Date: Tue, 11 Oct 2016 22:22:33 +0200 Subject: [PATCH 19/20] Changed event from map to atom, fixed seeks option --- lib/entice/logic/map_instance.ex | 2 +- lib/entice/logic/movement.ex | 12 ++++++------ lib/entice/logic/npc.ex | 4 ++-- test/entice/logic/ai/seek_test.exs | 2 +- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/lib/entice/logic/map_instance.ex b/lib/entice/logic/map_instance.ex index e286e3d..e2bb310 100644 --- a/lib/entice/logic/map_instance.ex +++ b/lib/entice/logic/map_instance.ex @@ -51,7 +51,7 @@ defmodule Entice.Logic.MapInstance do def handle_event( {:map_instance_npc_add, %{name: name, model: model, position: position}}, %Entity{attributes: %{MapInstance => %MapInstance{map: map}}} = entity) do - {:ok, eid, _pid} = Npc.spawn(name, model, position, [:seeks]) + {:ok, eid, _pid} = Npc.spawn(name, model, position, seeks: true) Coordination.register(eid, map) # TODO change map to something else if we have multiple instances {:ok, entity} end diff --git a/lib/entice/logic/movement.ex b/lib/entice/logic/movement.ex index cd7ffdc..e049f8c 100644 --- a/lib/entice/logic/movement.ex +++ b/lib/entice/logic/movement.ex @@ -17,8 +17,8 @@ defmodule Entice.Logic.Movement do def register(entity), do: Entity.put_behaviour(entity, Movement.Behaviour, []) - def register(entity, %{auto_updating?: auto_updating?}), - do: Entity.put_behaviour(entity, Movement.Behaviour, %{auto_updating?: auto_updating?}) + def register(entity, auto_updating?: auto_updating?), + do: Entity.put_behaviour(entity, Movement.Behaviour, auto_updating?: auto_updating?) def unregister(entity), do: Entity.remove_behaviour(entity, Movement.Behaviour) @@ -44,8 +44,8 @@ defmodule Entice.Logic.Movement do def init(%Entity{attributes: %{Movement => _}} = entity, _args), do: {:ok, entity} - def init(%Entity{attributes: %{Position => %Position{coord: coord, plane: plane}}} = entity, %{auto_updating?: true}) do - self |> Process.send_after({:movement_calculate_next}, 1) + def init(%Entity{attributes: %{Position => %Position{coord: coord, plane: plane}}} = entity, auto_updating?: true) do + self |> Process.send_after(:movement_calculate_next, 1) {:ok, entity |> put_attribute(%Movement{goal: coord, plane: plane, auto_updating?: true})} end @@ -55,10 +55,10 @@ defmodule Entice.Logic.Movement do def init(entity, _args), do: {:ok, entity |> put_attribute(%Movement{})} - def handle_event({:movement_calculate_next}, + def handle_event(:movement_calculate_next, %Entity{attributes: %{Movement => %Movement{auto_updating?: auto_updating?}}} = entity) do #TODO: implement once the whole collision business is handled - if auto_updating?, do: self |> Process.send_after({:movement_calculate_next}, Movement.update_interval) + if auto_updating?, do: self |> Process.send_after(:movement_calculate_next, Movement.update_interval) {:ok, entity} end diff --git a/lib/entice/logic/npc.ex b/lib/entice/logic/npc.ex index 23e719e..7a629f8 100644 --- a/lib/entice/logic/npc.ex +++ b/lib/entice/logic/npc.ex @@ -12,9 +12,9 @@ defmodule Entice.Logic.Npc do {:ok, id, pid} = Entity.start() Npc.register(id, name, model, position) Vitals.register(id) - if opts[:seeks] || true do + if opts[:seeks] do Seek.register(id) - Movement.register(id, %{auto_updating?: true}) + Movement.register(id, auto_updating?: true) else Movement.register(id) end diff --git a/test/entice/logic/ai/seek_test.exs b/test/entice/logic/ai/seek_test.exs index 8dc3060..e672c6a 100644 --- a/test/entice/logic/ai/seek_test.exs +++ b/test/entice/logic/ai/seek_test.exs @@ -12,7 +12,7 @@ defmodule Entice.Logic.SeekTest do setup do npc_init_coord = %Coord{x: 10, y: 10} - {:ok, npc_eid, npc_pid} = Npc.spawn("Dhuum", :dhuum, %Position{coord: npc_init_coord}) + {:ok, npc_eid, npc_pid} = Npc.spawn("Dhuum", :dhuum, %Position{coord: npc_init_coord}, seeks: true) {:ok, player_eid, player_pid} = Entity.start #Sets an initial pos for the player so the Position attr appears as changed and not added in the following tests simulate_movement_update(player_pid, %Position{coord: %Coord{x: 1, y: 2}}, %Movement{goal: %Coord{x: 3, y: 4}}) From e6dc0a63f52f4301a59201954ee9d06e41636fd5 Mon Sep 17 00:00:00 2001 From: EtienneDesticourt Date: Fri, 14 Oct 2016 13:44:03 +0200 Subject: [PATCH 20/20] Added geom deps, replaced Coord with Vector2D, added NavMesh to maps --- lib/entice/logic/ai/seek.ex | 43 ++++++++++-------- lib/entice/logic/attributes.ex | 2 +- lib/entice/logic/map_instance.ex | 2 +- lib/entice/logic/maps/map.ex | 13 +++--- lib/entice/logic/maps/maps.ex | 12 +++--- lib/entice/logic/movement.ex | 67 ++++++++++++++++++++++++++--- lib/entice/logic/npc.ex | 13 +++--- lib/entice/logic/player.ex | 4 +- mix.exs | 3 +- mix.lock | 2 + test/entice/logic/ai/seek_test.exs | 28 ++++++------ test/entice/logic/movement_test.exs | 14 +++--- test/entice/logic/npc_test.exs | 7 +-- 13 files changed, 141 insertions(+), 69 deletions(-) diff --git a/lib/entice/logic/ai/seek.ex b/lib/entice/logic/ai/seek.ex index b13242f..b173b27 100644 --- a/lib/entice/logic/ai/seek.ex +++ b/lib/entice/logic/ai/seek.ex @@ -1,9 +1,10 @@ defmodule Entice.Logic.Seek do alias Entice.Entity alias Entice.Logic.{Seek, Player.Position, Npc, Movement} - alias Entice.Utils.Geom.Coord + alias Geom.Shape.{Path, Vector} + alias Geom.Ai.Astar - defstruct target: nil, aggro_distance: 1000, escape_distance: 2000 + defstruct target: nil, aggro_distance: 1000, escape_distance: 2000, path: [] def register(entity), do: Entity.put_behaviour(entity, Seek.Behaviour, []) @@ -44,7 +45,7 @@ defmodule Entice.Logic.Seek do ^moving_entity_id -> if past_escape_range?(init_coord, mover_coord, escape_distance) do - {:ok, entity |> return_to_spawn(init_coord)} + {:ok, entity |> return_to_spawn(my_coord, init_coord)} else {:ok, entity |> seek_target_current_coord(moving_entity_id, mover_coord)} end @@ -57,26 +58,34 @@ defmodule Entice.Logic.Seek do do: {:ok, entity |> remove_attribute(Seek)} defp in_aggro_range?(my_coord, mover_coord, aggro_distance), - do: calc_distance(my_coord, mover_coord) <= aggro_distance + do: Vector.dist(my_coord, mover_coord) <= aggro_distance defp past_escape_range?(init_coord, mover_coord, escape_distance), - do: calc_distance(init_coord, mover_coord) >= escape_distance + do: Vector.dist(init_coord, mover_coord) >= escape_distance - defp seek_target_current_coord(entity, target_id, target_coord) do - entity - |> update_attribute(Seek, fn(s) -> %Seek{s | target: target_id} end) - |> update_attribute(Movement, fn(m) -> %Movement{m | goal: target_coord} end) - end + defp seek_target_current_coord(%Entity{attributes: %{Position => %Position{coord: my_coord}, + Npc => %Npc{map: map}, Seek => %Seek{path: path}}} = entity, target_id, target_coord) do + {success, result} = Astar.get_path(map.nav_mesh, my_coord, target_coord) + + %Path{vertices: new_path} = case success do + :ok -> result + + :error -> Path.empty + end - defp return_to_spawn(entity, spawn_coord) do - entity - |> update_attribute(Seek, fn(s) -> %Seek{s | target: nil} end) - |> update_attribute(Movement, fn(m) -> %Movement{m | goal: spawn_coord} end) + entity |> update_attribute(Seek, fn(s) -> %Seek{s | target: target_id, path: new_path} end) end - #TODO: Should probably move to Coord in Utils - defp calc_distance(%Coord{x: x1, y: y1}, %Coord{x: x2, y: y2}) do - :math.sqrt(:math.pow((x2-x1), 2) + :math.pow((y2-y1), 2)) + defp return_to_spawn(%Entity{attributes: %{Npc => %Npc{map: map}}} = entity, my_coord, spawn_coord) do + {success, result} = Astar.get_path(map.nav_mesh, my_coord, spawn_coord) + + %Path{vertices: new_path} = case success do + :ok -> result + + :error -> Path.empty + end + + entity |> update_attribute(Seek, fn(s) -> %Seek{s | target: nil, path: new_path} end) end end end diff --git a/lib/entice/logic/attributes.ex b/lib/entice/logic/attributes.ex index 8ca2090..839a277 100644 --- a/lib/entice/logic/attributes.ex +++ b/lib/entice/logic/attributes.ex @@ -5,7 +5,7 @@ defmodule Entice.Logic.Attributes do defmacro __using__(_) do quote do - alias Entice.Utils.Geom.Coord + alias Geom.Shape.Vector2D alias Entice.Logic.Player.Name alias Entice.Logic.Player.Position alias Entice.Logic.Player.MapInstance diff --git a/lib/entice/logic/map_instance.ex b/lib/entice/logic/map_instance.ex index e2bb310..4524506 100644 --- a/lib/entice/logic/map_instance.ex +++ b/lib/entice/logic/map_instance.ex @@ -51,7 +51,7 @@ defmodule Entice.Logic.MapInstance do def handle_event( {:map_instance_npc_add, %{name: name, model: model, position: position}}, %Entity{attributes: %{MapInstance => %MapInstance{map: map}}} = entity) do - {:ok, eid, _pid} = Npc.spawn(name, model, position, seeks: true) + {:ok, eid, _pid} = Npc.spawn(map, name, model, position, seeks: true) Coordination.register(eid, map) # TODO change map to something else if we have multiple instances {:ok, entity} end diff --git a/lib/entice/logic/maps/map.ex b/lib/entice/logic/maps/map.ex index f04b123..ffa54f5 100644 --- a/lib/entice/logic/maps/map.ex +++ b/lib/entice/logic/maps/map.ex @@ -4,7 +4,7 @@ defmodule Entice.Logic.Map do Is mainly used in area.ex where all the maps are defined. """ import Inflex - alias Entice.Utils.Geom.Coord + alias Geom.Shape.Vector2D defmacro __using__(_) do quote do @@ -17,16 +17,18 @@ defmodule Entice.Logic.Map do defmacro defmap(mapname, opts \\ []) do - spawn = Keyword.get(opts, :spawn, quote do %Coord{} end) + spawn = Keyword.get(opts, :spawn, quote do %Vector2D{} end) outpost = Keyword.get(opts, :outpost, quote do true end) + nav_mesh = Keyword.get(opts, :nav_mesh, quote do nil end) map_content = content(Macro.to_string(mapname)) quote do defmodule unquote(mapname) do - alias Entice.Utils.Geom.Coord + alias Geom.Shape.Vector2D unquote(map_content) def spawn, do: unquote(spawn) def is_outpost?, do: unquote(outpost) + def nav_mesh, do: unquote(nav_mesh) end @maps [ unquote(mapname) | @maps ] end @@ -58,10 +60,11 @@ defmodule Entice.Logic.Map do def name, do: unquote(name) def underscore_name, do: unquote(uname) - def spawn, do: %Coord{} + def spawn, do: %Vector2D{} def is_outpost?, do: true + def nav_mesh, do: nil - defoverridable [spawn: 0, is_outpost?: 0] + defoverridable [spawn: 0, is_outpost?: 0, nav_mesh: 0] end end end diff --git a/lib/entice/logic/maps/maps.ex b/lib/entice/logic/maps/maps.ex index b6ee84c..7bbf105 100644 --- a/lib/entice/logic/maps/maps.ex +++ b/lib/entice/logic/maps/maps.ex @@ -1,18 +1,20 @@ defmodule Entice.Logic.Maps do use Entice.Logic.Map + alias Geom.Shape.NavigationMesh, as: NavMesh + alias Geom.Utils.Serializer # Lobby is for special client entities that represent a logged in client. defmap Lobby # Outposts... - defmap HeroesAscent, spawn: %Coord{x: 2017, y: -3241} - defmap RandomArenas, spawn: %Coord{x: 3854, y: 3874} - defmap TeamArenas, spawn: %Coord{x: -1873, y: 352} + defmap HeroesAscent, spawn: %Vector2D{x: 2017, y: -3241}, nav_mesh: Serializer.deserialize(%NavMesh{}, "data/heroes_ascent.map"), outpost: true + defmap RandomArenas, spawn: %Vector2D{x: 3854, y: 3874} + defmap TeamArenas, spawn: %Vector2D{x: -1873, y: 352} # Explorables... - defmap GreatTempleOfBalthazar, spawn: %Coord{x: -6558, y: -6010}, outpost: false # faked for testing purpose - defmap IsleOfTheNameless, spawn: %Coord{x: -6036, y: -2519}, outpost: false + defmap GreatTempleOfBalthazar, spawn: %Vector2D{x: -6558, y: -6010}, outpost: false # faked for testing purpose + defmap IsleOfTheNameless, spawn: %Vector2D{x: -6036, y: -2519}, outpost: false def default_map, do: HeroesAscent diff --git a/lib/entice/logic/movement.ex b/lib/entice/logic/movement.ex index e049f8c..ae2ab0d 100644 --- a/lib/entice/logic/movement.ex +++ b/lib/entice/logic/movement.ex @@ -1,17 +1,17 @@ defmodule Entice.Logic.Movement do alias Entice.Entity - alias Entice.Utils.Geom.Coord - alias Entice.Logic.{Movement, Player.Position} - + alias Entice.Logic.{Movement, Player.Position, Seek} + alias Geom.Shape.{Vector, Vector2D} @update_interval 50 - + @speed 288 #TODO: Figure out the velocity/speed naming business + @epsilon 10 @doc """ Note that velocity is actually a coefficient for the real velocity thats used inside the client, but for simplicities sake we used velocity as a name. """ - defstruct goal: %Coord{}, plane: 1, move_type: 9, velocity: 1.0, auto_updating?: false + defstruct goal: %Vector2D{x: 0, y: 0}, plane: 1, move_type: 9, velocity: 1.0, auto_updating?: false def register(entity), @@ -36,6 +36,8 @@ defmodule Entice.Logic.Movement do end def update_interval, do: @update_interval + def speed, do: @speed + def epsilon, do: @epsilon defmodule Behaviour do @@ -55,13 +57,64 @@ defmodule Entice.Logic.Movement do def init(entity, _args), do: {:ok, entity |> put_attribute(%Movement{})} + #TODO: Move all this logic to seek def handle_event(:movement_calculate_next, - %Entity{attributes: %{Movement => %Movement{auto_updating?: auto_updating?}}} = entity) do - #TODO: implement once the whole collision business is handled + %Entity{attributes: %{Movement => %Movement{velocity: velocity, auto_updating?: auto_updating?, goal: goal}, + Seek => %Seek{path: path}, + Position => %Position{coord: current_pos}}} = entity) do + #Determine next goal + {entity, goal} = case Enum.count(path) do + 0 -> + {entity, goal} #Empty path + + 1 -> + {entity, goal} #Path only has starting pos left + + _ -> + cond do + Vector.equal(goal, current_pos, Movement.epsilon) -> #Reached goal + new_goal = Enum.at(path, 1) #len path >= 2 so we know next is not nil + entity = entity + |> update_attribute(Movement, fn(m) -> %Movement{m | goal: new_goal} end) + |> update_attribute(Seek, fn(s) -> %Seek{s | path: Enum.drop(path, 1)} end) + {entity, new_goal} + + true -> + {entity, goal} + end + end + + #Advance pos if not at new (or unchanged) goal + next_pos = cond do + Vector.equal(goal, current_pos, Movement.epsilon) -> + current_pos + + true -> + {:ok, next_pos} = calc_next_pos(current_pos, goal, velocity) + next_pos + end + + entity = entity |> update_attribute(Position, fn(p) -> %Position{p | coord: next_pos} end) if auto_updating?, do: self |> Process.send_after(:movement_calculate_next, Movement.update_interval) {:ok, entity} end + defp calc_next_pos(%Vector2D{} = current_pos, %Vector2D{} = goal, velocity) do + direction = Vector.sub(goal, current_pos) + cond do + #Convoluted cond because somehow %Vector2D{x: 0, y: 0} != %Vector2D{x: 0.0, y: 0.0} in case + Vector.equal(direction, %Vector2D{x: 0, y: 0}, 0) -> + {:ok, current_pos} + + true -> + unit = Vector.unit(direction) + offset = Vector.mul(unit, velocity * Movement.speed * Movement.update_interval / 1000) + {:ok, Vector.add(current_pos, offset)} + end + end + + defp calc_next_pos(_,_,_), do: {:error, :wrong_arguments} + def terminate(_reason, entity), do: {:ok, entity |> remove_attribute(Movement)} end diff --git a/lib/entice/logic/npc.ex b/lib/entice/logic/npc.ex index 7a629f8..c2db68a 100644 --- a/lib/entice/logic/npc.ex +++ b/lib/entice/logic/npc.ex @@ -4,13 +4,14 @@ defmodule Entice.Logic.Npc do alias Entice.Logic.{Npc, Vitals, Movement, Seek} alias Entice.Logic.Player.{Name, Position, Level} - defstruct npc_model_id: :dhuum, init_coord: %Position{} + defstruct npc_model_id: :dhuum, init_coord: %Position{}, map: nil - - def spawn(name, model, %Position{} = position, opts \\ []) + #I'd rather pass something like fn(pos1, pos2) -> Geom.Ai.Astar.findpath(map.nav_mesh, pos1, pos2) end + #from map_instance and then override func in this module but can't figure it out + def spawn(map, name, model, %Position{} = position, opts \\ []) when is_binary(name) and is_atom(model) do {:ok, id, pid} = Entity.start() - Npc.register(id, name, model, position) + Npc.register(id, map, name, model, position) Vitals.register(id) if opts[:seeks] do Seek.register(id) @@ -22,13 +23,13 @@ defmodule Entice.Logic.Npc do end - def register(entity, name, model, %Position{} = position) + def register(entity, map, name, model, %Position{} = position) when is_binary(name) and is_atom(model) do entity |> Entity.attribute_transaction(fn (attrs) -> attrs |> Map.put(Name, %Name{name: name}) |> Map.put(Position, position) - |> Map.put(Npc, %Npc{npc_model_id: model, init_coord: position.coord}) + |> Map.put(Npc, %Npc{npc_model_id: model, init_coord: position.coord, map: map}) |> Map.put(Level, %Level{level: 20}) end) end diff --git a/lib/entice/logic/player.ex b/lib/entice/logic/player.ex index 3d7b765..99859ff 100644 --- a/lib/entice/logic/player.ex +++ b/lib/entice/logic/player.ex @@ -3,14 +3,14 @@ defmodule Entice.Logic.Player do Responsible for the basic player stats. """ alias Entice.Entity - alias Entice.Utils.Geom.Coord + alias Geom.Shape.Vector2D defmodule Name, do: defstruct( name: "Unknown Entity") defmodule Position, do: defstruct( - coord: %Coord{}, + coord: %Vector2D{}, plane: 1) defmodule Appearance, do: defstruct( diff --git a/mix.exs b/mix.exs index 558ddf1..2f45aad 100644 --- a/mix.exs +++ b/mix.exs @@ -16,6 +16,7 @@ defmodule Entice.Logic.Mixfile do [{:entice_entity, github: "entice/entity", ref: "c26f6f77ae650e25e6cd2ffea8aae46b7d83966a"}, {:uuid, "~> 1.1"}, {:inflex, "~> 1.5"}, - {:pipe, "~> 0.0.2"}] + {:pipe, "~> 0.0.2"}, + {:geom, "~> 1.0"}] end end diff --git a/mix.lock b/mix.lock index bcfa7b8..1cb047b 100644 --- a/mix.lock +++ b/mix.lock @@ -1,5 +1,7 @@ %{"entice_entity": {:git, "https://github.com/entice/entity.git", "c26f6f77ae650e25e6cd2ffea8aae46b7d83966a", [ref: "c26f6f77ae650e25e6cd2ffea8aae46b7d83966a"]}, "entice_utils": {:git, "https://github.com/entice/utils.git", "79ead4dca77324b4c24f584468edbaff2029eeab", [ref: "79ead4dca77324b4c24f584468edbaff2029eeab"]}, + "geom": {:hex, :geom, "1.0.0", "b95356b34025b71096db92a6c3be47e7729db6983223a6d03cdff85aec01c3f5", [:mix], [{:poison, "~> 2.0 or ~>3.0", [hex: :poison, optional: false]}]}, "inflex": {:hex, :inflex, "1.5.0", "e4ff5d900280b2011b24d1ac1c4590986ee5add2ea644c9894e72213cf93ff0b", [:mix], []}, "pipe": {:hex, :pipe, "0.0.2", "eff98a868b426745acef103081581093ff5c1b88100f8ff5949b4a30e81d0d9f", [:mix], []}, + "poison": {:hex, :poison, "3.0.0", "625ebd64d33ae2e65201c2c14d6c85c27cc8b68f2d0dd37828fde9c6920dd131", [:mix], []}, "uuid": {:hex, :uuid, "1.1.3", "06ca38801a1a95b751701ca40716bb97ddf76dfe7e26da0eec7dba636740d57a", [:mix], []}} diff --git a/test/entice/logic/ai/seek_test.exs b/test/entice/logic/ai/seek_test.exs index e672c6a..8249524 100644 --- a/test/entice/logic/ai/seek_test.exs +++ b/test/entice/logic/ai/seek_test.exs @@ -11,11 +11,11 @@ defmodule Entice.Logic.SeekTest do defmap TestMap setup do - npc_init_coord = %Coord{x: 10, y: 10} - {:ok, npc_eid, npc_pid} = Npc.spawn("Dhuum", :dhuum, %Position{coord: npc_init_coord}, seeks: true) + npc_init_coord = %Vector2D{x: 10, y: 10} + {:ok, npc_eid, npc_pid} = Npc.spawn(TestMap, "Dhuum", :dhuum, %Position{coord: npc_init_coord}, seeks: true) {:ok, player_eid, player_pid} = Entity.start #Sets an initial pos for the player so the Position attr appears as changed and not added in the following tests - simulate_movement_update(player_pid, %Position{coord: %Coord{x: 1, y: 2}}, %Movement{goal: %Coord{x: 3, y: 4}}) + simulate_movement_update(player_pid, %Position{coord: %Vector2D{x: 1, y: 2}}, %Movement{goal: %Vector2D{x: 3, y: 4}}) Player.register(player_pid, TestMap) Coordination.register(player_eid, TestMap) @@ -28,22 +28,22 @@ defmodule Entice.Logic.SeekTest do end test "update same entity", %{npc_entity: pid} do - simulate_movement_update(pid, %Position{coord: %Coord{x: 1, y: 2}}, %Movement{goal: %Coord{x: 3, y: 4}}) + simulate_movement_update(pid, %Position{coord: %Vector2D{x: 1, y: 2}}, %Movement{goal: %Vector2D{x: 3, y: 4}}) assert {:ok, %Seek{target: nil}} = Entity.fetch_attribute(pid, Seek) end test "update no target, entity close enough to aggro", %{npc_entity: npc_pid, player_eid: player_eid, player_entity: player_pid} do #Move player within 2 units of distance of npc with aggro distance of 1000 - mover_coord = %Coord{x: 14, y: 10} + mover_coord = %Vector2D{x: 14, y: 10} simulate_movement_update(player_pid, %Position{coord: mover_coord}, %Movement{goal: mover_coord}) assert {:ok, %Seek{target: ^player_eid}} = Entity.fetch_attribute(npc_pid, Seek) - assert {:ok, %Movement{goal: ^mover_coord}} = Entity.fetch_attribute(npc_pid, Movement) + #assert {:ok, %Movement{goal: ^mover_coord}} = Entity.fetch_attribute(npc_pid, Movement) end test "update no target, entity too far to aggro", %{npc_entity: npc_pid, player_entity: player_pid} do - simulate_movement_update(player_pid, %Position{coord: %Coord{x: 460, y: 910}}, %Movement{goal: %Coord{x: 0, y: 0}}) + simulate_movement_update(player_pid, %Position{coord: %Vector2D{x: 460, y: 910}}, %Movement{goal: %Vector2D{x: 0, y: 0}}) assert {:ok, %Seek{target: nil}} = Entity.fetch_attribute(npc_pid, Seek) end @@ -53,11 +53,11 @@ defmodule Entice.Logic.SeekTest do #Create unrelated entity at a random pos and add it to map channel {:ok, other_player_eid, other_player_pid} = Entity.start - simulate_movement_update(other_player_pid, %Position{coord: %Coord{x: 1, y: 2}}, %Movement{goal: %Coord{x: 3, y: 4}}) + simulate_movement_update(other_player_pid, %Position{coord: %Vector2D{x: 1, y: 2}}, %Movement{goal: %Vector2D{x: 3, y: 4}}) Coordination.register(other_player_eid, TestMap) #Move other player inside of aggro range and check that npc keeps initial target - simulate_movement_update(other_player_pid, %Position{coord: %Coord{x: 11, y: 11}}, %Movement{goal: %Coord{x: 0, y: 0}}) + simulate_movement_update(other_player_pid, %Position{coord: %Vector2D{x: 11, y: 11}}, %Movement{goal: %Vector2D{x: 0, y: 0}}) assert {:ok, %Seek{target: ^player_eid}} = Entity.fetch_attribute(npc_pid, Seek) end @@ -67,9 +67,9 @@ defmodule Entice.Logic.SeekTest do Entity.put_attribute(npc_pid, %Seek{target: player_eid, aggro_distance: 10, escape_distance: 20}) #Move target player far enough to escape - simulate_movement_update(player_pid, %Position{coord: %Coord{x: 30, y: 15}}, %Movement{goal: %Coord{x: 0, y: 0}}) + simulate_movement_update(player_pid, %Position{coord: %Vector2D{x: 30, y: 15}}, %Movement{goal: %Vector2D{x: 0, y: 0}}) assert {:ok, %Seek{target: nil}} = Entity.fetch_attribute(npc_pid, Seek) - assert {:ok, %Movement{goal: ^npc_init_coord}} = Entity.fetch_attribute(npc_pid, Movement) + #assert {:ok, %Movement{goal: ^npc_init_coord}} = Entity.fetch_attribute(npc_pid, Movement) end test "update has target, entity is current target, entity does not escape", %{npc_entity: npc_pid, player_eid: player_eid, player_entity: player_pid} do @@ -77,10 +77,10 @@ defmodule Entice.Logic.SeekTest do Entity.put_attribute(npc_pid, %Seek{target: player_eid, aggro_distance: 10, escape_distance: 20}) #Move target player but not far enough to escape - new_player_coord = %Coord{x: 14, y: 15} - simulate_movement_update(player_pid, %Position{coord: new_player_coord}, %Movement{goal: %Coord{x: 0, y: 0}}) + new_player_coord = %Vector2D{x: 14, y: 15} + simulate_movement_update(player_pid, %Position{coord: new_player_coord}, %Movement{goal: %Vector2D{x: 0, y: 0}}) assert {:ok, %Seek{target: ^player_eid}} = Entity.fetch_attribute(npc_pid, Seek) - assert {:ok, %Movement{goal: ^new_player_coord}} = Entity.fetch_attribute(npc_pid, Movement) + #assert {:ok, %Movement{goal: ^new_player_coord}} = Entity.fetch_attribute(npc_pid, Movement) end diff --git a/test/entice/logic/movement_test.exs b/test/entice/logic/movement_test.exs index 70be2ec..8743ef4 100644 --- a/test/entice/logic/movement_test.exs +++ b/test/entice/logic/movement_test.exs @@ -1,9 +1,9 @@ defmodule Entice.Logic.MovementTest do use ExUnit.Case, async: true alias Entice.Entity - alias Entice.Utils.Geom.Coord alias Entice.Logic.Movement alias Entice.Logic.Player.Position + alias Geom.Shape.Vector2D setup do @@ -21,26 +21,26 @@ defmodule Entice.Logic.MovementTest do test "register with position", %{entity: pid} do Movement.unregister(pid) # remove again, so we can add a new one - Entity.put_attribute(pid, %Position{coord: %Coord{x: 42, y: 1337}, plane: 7}) + Entity.put_attribute(pid, %Position{coord: %Vector2D{x: 42, y: 1337}, plane: 7}) Movement.register(pid) - m = %Movement{goal: %Coord{x: 42, y: 1337}, plane: 7} + m = %Movement{goal: %Vector2D{x: 42, y: 1337}, plane: 7} assert {:ok, ^m} = Entity.fetch_attribute(pid, Movement) end test "register with movement", %{entity: pid} do Movement.unregister(pid) # remove again, so we can add a new one - Entity.put_attribute(pid, %Movement{goal: %Coord{x: 42, y: 1337}}) + Entity.put_attribute(pid, %Movement{goal: %Vector2D{x: 42, y: 1337}}) Movement.register(pid) - m = %Movement{goal: %Coord{x: 42, y: 1337}} + m = %Movement{goal: %Vector2D{x: 42, y: 1337}} assert {:ok, ^m} = Entity.fetch_attribute(pid, Movement) end test "update", %{entity: pid} do Movement.update(pid, - %Position{coord: %Coord{x: 42, y: 1337}, plane: 7}, - %Movement{goal: %Coord{x: 1337, y: 42}, plane: 13, move_type: 5, velocity: 0.5}) + %Position{coord: %Vector2D{x: 42, y: 1337}, plane: 7}, + %Movement{goal: %Vector2D{x: 1337, y: 42}, plane: 13, move_type: 5, velocity: 0.5}) assert {:ok, %Position{plane: 7}} = Entity.fetch_attribute(pid, Position) assert {:ok, %Movement{move_type: 5}} = Entity.fetch_attribute(pid, Movement) end diff --git a/test/entice/logic/npc_test.exs b/test/entice/logic/npc_test.exs index 33284e0..a412629 100644 --- a/test/entice/logic/npc_test.exs +++ b/test/entice/logic/npc_test.exs @@ -8,15 +8,16 @@ defmodule Entice.Logic.NpcTest do setup do - {:ok, _id, pid} = Npc.spawn("Dhuum", :dhuum, %Position{coord: %Coord{x: 1, y: 2}, plane: 3}) + #IO.inspect HeroesAscent.nav_mesh + {:ok, _id, pid} = Npc.spawn(HeroesAscent, "Dhuum", :dhuum, %Position{coord: %Vector2D{x: 1, y: 2}, plane: 3}) {:ok, [entity: pid]} end - test "correct spawn", %{entity: pid} do + test "correct spawn", %{entity: pid} do assert {:ok, %Name{name: "Dhuum"}} = Entity.fetch_attribute(pid, Name) assert {:ok, %Npc{npc_model_id: :dhuum}} = Entity.fetch_attribute(pid, Npc) assert {:ok, %Level{level: 20}} = Entity.fetch_attribute(pid, Level) - assert {:ok, %Position{coord: %Coord{x: 1, y: 2}, plane: 3}} = Entity.fetch_attribute(pid, Position) + assert {:ok, %Position{coord: %Vector2D{x: 1, y: 2}, plane: 3}} = Entity.fetch_attribute(pid, Position) end test "correct unregister", %{entity: pid} do