Skip to content

Commit

Permalink
chore: cleanup and add test case
Browse files Browse the repository at this point in the history
  • Loading branch information
Fizo55 committed Oct 10, 2023
1 parent 6b6eee9 commit 370af4b
Show file tree
Hide file tree
Showing 4 changed files with 135 additions and 89 deletions.
2 changes: 1 addition & 1 deletion apps/game_service/lib/game_service/bundles/monster.ex
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ defmodule GameService.MonsterBundle do
@doc """
This function can be use to create a MonsterBundle from an Entity an a list of components
Unlike `load/2`, you don't have to provide all components.
Unlike `load/2`, you don't have to provide all components.
Components not found will have the value `:unset`
NOTE: You must verify that you have the required components in your system.
Expand Down
93 changes: 40 additions & 53 deletions apps/game_service/lib/game_service/systems/entity_message.ex
Original file line number Diff line number Diff line change
@@ -1,53 +1,40 @@
defmodule GameService.EntityMessageSystem do
@moduledoc """
TODO: Documentation for GameService.EntityMessageSystem
"""

use GameService.System,
lock_components: [],
event_subscriptions: [
GameService.Events.EntityMessage
]

require Logger

alias GameService.Events.EntityMessage

# System behaviour

@impl true
def run(%EntityMessage{scope: :map} = event, _context) do
# Creating our event message, to send map message
%EntityMessage{
entity_type: entity_type,
entity_id: entity_id,
message: message
} = event

# In the GameService, Entity's id is a combination of it's type and it's id
ecs_id = GameService.real_entity_id(entity_type, entity_id)

# Check if the Entity exists
with {:ok, entity} <- Query.fetch_entity(ecs_id),
{:ok, position} <- Query.fetch_component(entity, E.PositionComponent) do
# Finally, notify all players on map
event = {:chat_message, entity_type, entity_id, message}

# Here, the 3rd component means that we don't want to send the event to ourself
GameService.System.map_event(event, position, [entity])
end
|> maybe_print_error(event)
end

def run(event, _context) do
Logger.warn("#{inspect(__MODULE__)} unhandled event #{inspect(event)}")
end

## Helpers

defp maybe_print_error({:error, _} = error, event) do
System.error(__MODULE__, error, event)
end

defp maybe_print_error(_, _), do: :ok
end
defmodule GameService.EntityMessageSystem do
@moduledoc """
TODO: Documentation for GameService.EntityMessageSystem
"""

use GameService.System,
lock_components: [],
event_subscriptions: [
GameService.Events.EntityMessage
]

require Logger

alias GameService.Events.EntityMessage

# System behaviour

@impl true
def run(%EntityMessage{scope: :map} = event, _context) do
# Creating our event message, to send map message
%EntityMessage{
entity_type: entity_type,
entity_id: entity_id,
message: message
} = event

# In the GameService, Entity's id is a combination of it's type and it's id
ecs_id = GameService.real_entity_id(entity_type, entity_id)

# Check if the Entity exists
with {:ok, entity} <- Query.fetch_entity(ecs_id),
{:ok, position} <- Query.fetch_component(entity, E.PositionComponent) do
# Finally, notify all players on map
event = {:chat_message, entity_type, entity_id, message}

# Here, the 3rd component means that we don't want to send the event to ourself
GameService.System.map_event(event, position, [entity])
end
end
end
Original file line number Diff line number Diff line change
@@ -1,32 +1,53 @@
defmodule GameService.EntityMessageSystemTest do
use GameService.SystemCase

import ExUnit.CaptureLog

alias GameService.EntityMessageSystem

## Tests

describe "EntityMessage" do
test "system event on entity writting in general chat" do
# Register our process to receive message
ref = make_ref()
position = %E.PositionComponent{map_ref: ref}
endpoint = %P.EndpointComponent{pid: self()}
entity = spawn_player(components: [endpoint, position])

# Call our System with a EntityMessage event
event = %Evt.EntityMessage{
entity_type: :player,
entity_id: GameService.entity_id(entity),
scope: :map,
message: "Best message"
}

_ = EntityMessageSystem.run(event, 0)

# # We shouldn't receive an event
refute_received {:chat_message, _, _, _}
end
end
end
defmodule GameService.EntityMessageSystemTest do
use GameService.SystemCase

import ExUnit.CaptureLog

alias GameService.EntityMessageSystem

## Tests

describe "EntityMessage" do
test "system event on entity writting in general chat" do
# Register our process to receive message
ref = make_ref()
position = %E.PositionComponent{map_ref: ref}
endpoint = %P.EndpointComponent{pid: self()}
entity = spawn_player(components: [endpoint, position])

# Call our System with a EntityMessage event
event = %Evt.EntityMessage{
entity_type: GameService.entity_type(entity),
entity_id: GameService.entity_id(entity),
scope: :map,
message: "Best message"
}

_ = EntityMessageSystem.run(event, 0)

# # We shouldn't receive an event
refute_received {:chat_message, _, _, _}
end

test "system event on another entity writting in general chat" do
# Register our process to receive message
ref = make_ref()
position = %E.PositionComponent{map_ref: ref}
endpoint = %P.EndpointComponent{pid: self()}
entity = spawn_monster(components: [endpoint, position])

# Call our System with a EntityMessage event
event = %Evt.EntityMessage{
entity_type: GameService.entity_type(entity),
entity_id: GameService.entity_id(entity),
scope: :map,
message: "Best message"
}

_ = EntityMessageSystem.run(event, 0)

# # We shouldn't receive an event
refute_received {:chat_message, _, _, _}
end
end
end
44 changes: 41 additions & 3 deletions apps/game_service/test/support/system_case.ex
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,19 @@ defmodule GameService.SystemCase do
alias ElvenGard.ECS.{Entity, Command}
alias GameService.EntityComponents, as: E
alias GameService.PlayerComponents, as: P
alias GameService.MonsterComponents, as: M

using _ do
quote do
import unquote(__MODULE__), only: [spawn_player: 0, spawn_player: 1]
import unquote(__MODULE__),
only: [spawn_player: 0, spawn_player: 1, spawn_monster: 0, spawn_monster: 1]

alias ElvenGard.ECS.{Command, Entity, Query}

alias GameService.Events, as: Evt
alias GameService.EntityComponents, as: E
alias GameService.PlayerComponents, as: P
alias GameService.MonsterComponents, as: M
end
end

Expand All @@ -25,7 +28,7 @@ defmodule GameService.SystemCase do
|> Keyword.get(:components, [])
|> Enum.group_by(& &1.__struct__, & &1)

map_default = Enum.group_by(default_components(), & &1.__struct__, & &1)
map_default = Enum.group_by(default_player_components(), & &1.__struct__, & &1)

components =
map_default
Expand All @@ -43,9 +46,44 @@ defmodule GameService.SystemCase do
entity
end

def spawn_monster(attrs \\ []) do
map_components =
attrs
|> Keyword.get(:components, [])
|> Enum.group_by(& &1.__struct__, & &1)

map_default = Enum.group_by(default_monster_component(), & &1.__struct__, & &1)

components =
map_default
|> Map.merge(map_components)
|> Map.values()
|> List.flatten()

{:ok, {entity, _components}} =
attrs
|> Keyword.put(:components, components)
|> Entity.entity_spec()
|> Map.update!(:id, &{:monster, &1})
|> Command.spawn_entity()

entity
end

## Private function

defp default_components() do
defp default_monster_component() do
[
%M.MonsterComponent{name: "Danderito", vnum: 1, spawn_effect: :falling},
%E.PositionComponent{map_id: 123, map_ref: make_ref(), map_x: 50, map_y: 12},
%E.LevelComponent{value: 40, xp: 403_000, xp_max: 5_000_000},
%E.SpeedComponent{value: 14},
%E.DirectionComponent{value: :north},
%E.CombatComponent{hp: 40_000, hp_max: 44_444, mp: 30_000, mp_max: 33_333}
]
end

defp default_player_components() do
[
%P.AccountComponent{id: 123, username: "username", authority: :player},
%P.EndpointComponent{pid: elem(Task.start(fn -> :ok end), 1)},
Expand Down

0 comments on commit 370af4b

Please sign in to comment.