diff --git a/lib/entice/logic/ai/seek.ex b/lib/entice/logic/ai/seek.ex new file mode 100644 index 0000000..b173b27 --- /dev/null +++ b/lib/entice/logic/ai/seek.ex @@ -0,0 +1,91 @@ +defmodule Entice.Logic.Seek do + alias Entice.Entity + alias Entice.Logic.{Seek, Player.Position, Npc, Movement} + alias Geom.Shape.{Path, Vector} + alias Geom.Ai.Astar + + defstruct target: nil, aggro_distance: 1000, escape_distance: 2000, path: [] + + 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 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{})} + + #No introspection for npcs ;) + 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}}, + %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 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 + + ^moving_entity_id -> + if past_escape_range?(init_coord, mover_coord, escape_distance) do + {:ok, entity |> return_to_spawn(my_coord, init_coord)} + else + {:ok, entity |> seek_target_current_coord(moving_entity_id, mover_coord)} + end + + _ -> {:ok, entity} + end + end + + def terminate(_reason, entity), + do: {:ok, entity |> remove_attribute(Seek)} + + defp in_aggro_range?(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: Vector.dist(init_coord, mover_coord) >= escape_distance + + 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 + + entity |> update_attribute(Seek, fn(s) -> %Seek{s | target: target_id, path: new_path} end) + end + + 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 588310c..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) + {: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 c1d4430..ae2ab0d 100644 --- a/lib/entice/logic/movement.ex +++ b/lib/entice/logic/movement.ex @@ -1,19 +1,24 @@ 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 + defstruct goal: %Vector2D{x: 0, y: 0}, plane: 1, move_type: 9, velocity: 1.0, auto_updating?: false 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) @@ -30,6 +35,10 @@ defmodule Entice.Logic.Movement do end) end + def update_interval, do: @update_interval + def speed, do: @speed + def epsilon, do: @epsilon + defmodule Behaviour do use Entice.Entity.Behaviour @@ -37,12 +46,74 @@ 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, 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})} 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{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)} diff --git a/lib/entice/logic/npc.ex b/lib/entice/logic/npc.ex index f46df98..c2db68a 100644 --- a/lib/entice/logic/npc.ex +++ b/lib/entice/logic/npc.ex @@ -1,29 +1,35 @@ 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_coord: %Position{}, map: nil - defstruct(npc_model_id: :dhuum) - - - def spawn(name, model, %Position{} = position) + #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) + Movement.register(id, auto_updating?: true) + else + Movement.register(id) + end {:ok, id, pid} 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}) + |> 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 d3b5648..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( - pos: %Coord{}, + coord: %Vector2D{}, plane: 1) defmodule Appearance, do: defstruct( @@ -31,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/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 68c0655..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"]}, - "inflex": {:hex, :inflex, "1.5.0"}, - "pipe": {:hex, :pipe, "0.0.2"}, - "uuid": {:hex, :uuid, "1.1.3"}} + "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 new file mode 100644 index 0000000..8249524 --- /dev/null +++ b/test/entice/logic/ai/seek_test.exs @@ -0,0 +1,95 @@ +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 + use Entice.Logic.Map + + defmap TestMap + + setup do + 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: %Vector2D{x: 1, y: 2}}, %Movement{goal: %Vector2D{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_coord: npc_init_coord]} + end + + 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 + 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 = %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) + 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: %Vector2D{x: 460, y: 910}}, %Movement{goal: %Vector2D{x: 0, y: 0}}) + 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 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: %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: %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 + + 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}) + + #Move target player far enough to escape + 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) + 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 + #Set player as target + 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 = %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) + end + + + defp simulate_movement_update(entity_pid, new_coord, new_movement) do + entity_pid |> Entity.attribute_transaction( + fn attrs -> + attrs + |> Map.put(Position, new_coord) + |> 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 c49418a..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{pos: %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{pos: %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 abaf792..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{pos: %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{pos: %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