mirror of
https://github.com/wanderer-industries/wanderer
synced 2025-12-09 17:25:38 +00:00
If enabled and set, a temporary name is displayed instead of the system name. The original system name appears on a secondary row if no custom name exists. Temporary names are removed when the system is removed from the map.
516 lines
15 KiB
Elixir
516 lines
15 KiB
Elixir
defmodule WandererApp.Map.Server.SystemsImpl do
|
|
@moduledoc false
|
|
|
|
require Logger
|
|
|
|
alias WandererApp.Map.Server.{Impl}
|
|
|
|
@ddrt Application.compile_env(:wanderer_app, :ddrt)
|
|
@system_auto_expire_minutes 15
|
|
@system_inactive_timeout :timer.minutes(15)
|
|
|
|
def init_last_activity_cache(map_id, systems_last_activity) do
|
|
systems_last_activity
|
|
|> Enum.each(fn {system_id, last_activity} ->
|
|
WandererApp.Cache.put(
|
|
"map_#{map_id}:system_#{system_id}:last_activity",
|
|
last_activity,
|
|
ttl: @system_inactive_timeout
|
|
)
|
|
end)
|
|
end
|
|
|
|
def init_map_systems(state, [] = _systems), do: state
|
|
|
|
def init_map_systems(%{map_id: map_id, rtree_name: rtree_name} = state, systems) do
|
|
systems
|
|
|> Enum.each(fn %{id: system_id, solar_system_id: solar_system_id} = system ->
|
|
@ddrt.insert(
|
|
{solar_system_id, WandererApp.Map.PositionCalculator.get_system_bounding_rect(system)},
|
|
rtree_name
|
|
)
|
|
|
|
WandererApp.Cache.put(
|
|
"map_#{map_id}:system_#{system_id}:last_activity",
|
|
DateTime.utc_now(),
|
|
ttl: @system_inactive_timeout
|
|
)
|
|
end)
|
|
|
|
state
|
|
end
|
|
|
|
def add_system(
|
|
%{map_id: map_id} = state,
|
|
%{
|
|
solar_system_id: solar_system_id
|
|
} = system_info,
|
|
user_id,
|
|
character_id
|
|
) do
|
|
case map_id |> WandererApp.Map.check_location(%{solar_system_id: solar_system_id}) do
|
|
{:ok, _location} ->
|
|
state |> _add_system(system_info, user_id, character_id)
|
|
|
|
{:error, :already_exists} ->
|
|
state
|
|
end
|
|
end
|
|
|
|
def cleanup_systems(%{map_id: map_id} = state) do
|
|
expired_systems =
|
|
map_id
|
|
|> WandererApp.Map.list_systems!()
|
|
|> Enum.filter(fn %{
|
|
id: system_id,
|
|
visible: system_visible,
|
|
locked: system_locked,
|
|
solar_system_id: solar_system_id
|
|
} = _system ->
|
|
last_updated_time =
|
|
WandererApp.Cache.get("map_#{map_id}:system_#{system_id}:last_activity")
|
|
|
|
if system_visible and not system_locked and
|
|
(is_nil(last_updated_time) or
|
|
DateTime.diff(DateTime.utc_now(), last_updated_time, :minute) >=
|
|
@system_auto_expire_minutes) do
|
|
no_active_connections? =
|
|
map_id
|
|
|> WandererApp.Map.find_connections(solar_system_id)
|
|
|> Enum.empty?()
|
|
|
|
no_active_characters? =
|
|
map_id |> WandererApp.Map.get_system_characters(solar_system_id) |> Enum.empty?()
|
|
|
|
no_active_connections? and no_active_characters?
|
|
else
|
|
false
|
|
end
|
|
end)
|
|
|> Enum.map(& &1.solar_system_id)
|
|
|
|
case expired_systems |> Enum.empty?() do
|
|
false ->
|
|
state |> delete_systems(expired_systems, nil, nil)
|
|
|
|
_ ->
|
|
state
|
|
end
|
|
end
|
|
|
|
def update_system_name(
|
|
state,
|
|
update
|
|
),
|
|
do: state |> update_system(:update_name, [:name], update)
|
|
|
|
def update_system_description(
|
|
state,
|
|
update
|
|
),
|
|
do: state |> update_system(:update_description, [:description], update)
|
|
|
|
def update_system_status(
|
|
state,
|
|
update
|
|
),
|
|
do: state |> update_system(:update_status, [:status], update)
|
|
|
|
def update_system_tag(
|
|
state,
|
|
update
|
|
),
|
|
do: state |> update_system(:update_tag, [:tag], update)
|
|
|
|
def update_system_temporary_name(
|
|
state,
|
|
update
|
|
) do
|
|
state |> update_system(:update_temporary_name, [:temporary_name], update)
|
|
end
|
|
|
|
def update_system_locked(
|
|
state,
|
|
update
|
|
),
|
|
do: state |> update_system(:update_locked, [:locked], update)
|
|
|
|
def update_system_labels(
|
|
state,
|
|
update
|
|
),
|
|
do: state |> update_system(:update_labels, [:labels], update)
|
|
|
|
def update_system_position(
|
|
%{rtree_name: rtree_name} = state,
|
|
update
|
|
),
|
|
do:
|
|
state
|
|
|> update_system(
|
|
:update_position,
|
|
[:position_x, :position_y],
|
|
update,
|
|
fn updated_system ->
|
|
@ddrt.update(
|
|
updated_system.solar_system_id,
|
|
WandererApp.Map.PositionCalculator.get_system_bounding_rect(updated_system),
|
|
rtree_name
|
|
)
|
|
end
|
|
)
|
|
|
|
def add_hub(
|
|
%{map_id: map_id} = state,
|
|
hub_info
|
|
) do
|
|
with :ok <- WandererApp.Map.add_hub(map_id, hub_info),
|
|
{:ok, hubs} = map_id |> WandererApp.Map.list_hubs(),
|
|
{:ok, _} <-
|
|
WandererApp.MapRepo.update_hubs(map_id, hubs) do
|
|
Impl.broadcast!(map_id, :update_map, %{hubs: hubs})
|
|
state
|
|
else
|
|
error ->
|
|
Logger.error("Failed to add hub: #{inspect(error, pretty: true)}")
|
|
state
|
|
end
|
|
end
|
|
|
|
def remove_hub(
|
|
%{map_id: map_id} = state,
|
|
hub_info
|
|
) do
|
|
with :ok <- WandererApp.Map.remove_hub(map_id, hub_info),
|
|
{:ok, hubs} = map_id |> WandererApp.Map.list_hubs(),
|
|
{:ok, _} <-
|
|
WandererApp.MapRepo.update_hubs(map_id, hubs) do
|
|
Impl.broadcast!(map_id, :update_map, %{hubs: hubs})
|
|
state
|
|
else
|
|
error ->
|
|
Logger.error("Failed to remove hub: #{inspect(error, pretty: true)}")
|
|
state
|
|
end
|
|
end
|
|
|
|
def delete_systems(
|
|
%{map_id: map_id, rtree_name: rtree_name} = state,
|
|
removed_ids,
|
|
user_id,
|
|
character_id
|
|
) do
|
|
connections_to_remove =
|
|
removed_ids
|
|
|> Enum.map(fn solar_system_id ->
|
|
WandererApp.Map.find_connections(map_id, solar_system_id)
|
|
end)
|
|
|> List.flatten()
|
|
|> Enum.uniq_by(& &1.id)
|
|
|
|
:ok = WandererApp.Map.remove_connections(map_id, connections_to_remove)
|
|
:ok = WandererApp.Map.remove_systems(map_id, removed_ids)
|
|
|
|
removed_ids
|
|
|> Enum.each(fn solar_system_id ->
|
|
map_id
|
|
|> WandererApp.MapSystemRepo.remove_from_map(solar_system_id)
|
|
|> case do
|
|
{:ok, _} ->
|
|
:ok
|
|
|
|
{:error, error} ->
|
|
Logger.error("Failed to remove system from map: #{inspect(error, pretty: true)}")
|
|
:ok
|
|
end
|
|
end)
|
|
|
|
connections_to_remove
|
|
|> Enum.each(fn connection ->
|
|
Logger.debug(fn -> "Removing connection from map: #{inspect(connection)}" end)
|
|
WandererApp.MapConnectionRepo.destroy(map_id, connection)
|
|
end)
|
|
|
|
removed_ids
|
|
|> Enum.map(fn solar_system_id ->
|
|
WandererApp.Api.MapSystemSignature.by_linked_system_id!(solar_system_id)
|
|
end)
|
|
|> List.flatten()
|
|
|> Enum.uniq_by(& &1.system_id)
|
|
|> Enum.each(fn s ->
|
|
{:ok, %{system: system}} = s |> Ash.load([:system])
|
|
Ash.destroy!(s)
|
|
|
|
Impl.broadcast!(map_id, :signatures_updated, system.solar_system_id)
|
|
end)
|
|
|
|
@ddrt.delete(removed_ids, rtree_name)
|
|
|
|
Impl.broadcast!(map_id, :remove_connections, connections_to_remove)
|
|
Impl.broadcast!(map_id, :systems_removed, removed_ids)
|
|
|
|
case not is_nil(user_id) do
|
|
true ->
|
|
{:ok, _} =
|
|
WandererApp.User.ActivityTracker.track_map_event(:systems_removed, %{
|
|
character_id: character_id,
|
|
user_id: user_id,
|
|
map_id: map_id,
|
|
solar_system_ids: removed_ids
|
|
})
|
|
|
|
:telemetry.execute(
|
|
[:wanderer_app, :map, :systems, :remove],
|
|
%{count: removed_ids |> Enum.count()}
|
|
)
|
|
|
|
:ok
|
|
|
|
_ ->
|
|
:ok
|
|
end
|
|
|
|
state
|
|
end
|
|
|
|
def maybe_add_system(map_id, location, old_location, rtree_name, map_opts)
|
|
when not is_nil(location) do
|
|
case WandererApp.Map.check_location(map_id, location) do
|
|
{:ok, location} ->
|
|
{:ok, position} = calc_new_system_position(map_id, old_location, rtree_name, map_opts)
|
|
|
|
case WandererApp.MapSystemRepo.get_by_map_and_solar_system_id(
|
|
map_id,
|
|
location.solar_system_id
|
|
) do
|
|
{:ok, existing_system} when not is_nil(existing_system) ->
|
|
{:ok, updated_system} =
|
|
existing_system
|
|
|> WandererApp.MapSystemRepo.update_position!(%{
|
|
position_x: position.x,
|
|
position_y: position.y
|
|
})
|
|
|> WandererApp.MapSystemRepo.cleanup_labels!(map_opts)
|
|
|> WandererApp.MapSystemRepo.update_visible!(%{visible: true})
|
|
|> WandererApp.MapSystemRepo.cleanup_tags()
|
|
|> WandererApp.MapSystemRepo.cleanup_temporary_name()
|
|
|
|
@ddrt.insert(
|
|
{existing_system.solar_system_id,
|
|
WandererApp.Map.PositionCalculator.get_system_bounding_rect(%{
|
|
position_x: position.x,
|
|
position_y: position.y
|
|
})},
|
|
rtree_name
|
|
)
|
|
|
|
WandererApp.Cache.put(
|
|
"map_#{map_id}:system_#{updated_system.id}:last_activity",
|
|
DateTime.utc_now(),
|
|
ttl: @system_inactive_timeout
|
|
)
|
|
|
|
WandererApp.Map.add_system(map_id, updated_system)
|
|
|
|
Impl.broadcast!(map_id, :add_system, updated_system)
|
|
:ok
|
|
|
|
_ ->
|
|
{:ok, solar_system_info} =
|
|
WandererApp.CachedInfo.get_system_static_info(location.solar_system_id)
|
|
|
|
WandererApp.MapSystemRepo.create(%{
|
|
map_id: map_id,
|
|
solar_system_id: location.solar_system_id,
|
|
name: solar_system_info.solar_system_name,
|
|
position_x: position.x,
|
|
position_y: position.y
|
|
})
|
|
|> case do
|
|
{:ok, new_system} ->
|
|
@ddrt.insert(
|
|
{new_system.solar_system_id,
|
|
WandererApp.Map.PositionCalculator.get_system_bounding_rect(new_system)},
|
|
rtree_name
|
|
)
|
|
|
|
WandererApp.Cache.put(
|
|
"map_#{map_id}:system_#{new_system.id}:last_activity",
|
|
DateTime.utc_now(),
|
|
ttl: @system_inactive_timeout
|
|
)
|
|
|
|
WandererApp.Map.add_system(map_id, new_system)
|
|
Impl.broadcast!(map_id, :add_system, new_system)
|
|
|
|
:ok
|
|
|
|
error ->
|
|
Logger.warning("Failed to create system: #{inspect(error, pretty: true)}")
|
|
:ok
|
|
end
|
|
end
|
|
|
|
error ->
|
|
Logger.debug("Skip adding system: #{inspect(error, pretty: true)}")
|
|
:ok
|
|
end
|
|
end
|
|
|
|
def maybe_add_system(_map_id, _location, _old_location, _rtree_name, _map_opts), do: :ok
|
|
|
|
defp _add_system(
|
|
%{map_id: map_id, map_opts: map_opts, rtree_name: rtree_name} = state,
|
|
%{
|
|
solar_system_id: solar_system_id,
|
|
coordinates: coordinates
|
|
} = system_info,
|
|
user_id,
|
|
character_id
|
|
) do
|
|
%{"x" => x, "y" => y} =
|
|
coordinates
|
|
|> case do
|
|
%{"x" => x, "y" => y} ->
|
|
%{"x" => x, "y" => y}
|
|
|
|
_ ->
|
|
%{x: x, y: y} =
|
|
WandererApp.Map.PositionCalculator.get_new_system_position(nil, rtree_name, map_opts)
|
|
|
|
%{"x" => x, "y" => y}
|
|
end
|
|
|
|
{:ok, system} =
|
|
case WandererApp.MapSystemRepo.get_by_map_and_solar_system_id(map_id, solar_system_id) do
|
|
{:ok, existing_system} when not is_nil(existing_system) ->
|
|
use_old_coordinates = Map.get(system_info, :use_old_coordinates, false)
|
|
|
|
if use_old_coordinates do
|
|
@ddrt.insert(
|
|
{solar_system_id,
|
|
WandererApp.Map.PositionCalculator.get_system_bounding_rect(%{
|
|
position_x: existing_system.position_x,
|
|
position_y: existing_system.position_y
|
|
})},
|
|
rtree_name
|
|
)
|
|
|
|
existing_system
|
|
|> WandererApp.MapSystemRepo.update_visible(%{visible: true})
|
|
else
|
|
@ddrt.insert(
|
|
{solar_system_id,
|
|
WandererApp.Map.PositionCalculator.get_system_bounding_rect(%{
|
|
position_x: x,
|
|
position_y: y
|
|
})},
|
|
rtree_name
|
|
)
|
|
|
|
existing_system
|
|
|> WandererApp.MapSystemRepo.update_position!(%{position_x: x, position_y: y})
|
|
|> WandererApp.MapSystemRepo.cleanup_labels!(map_opts)
|
|
|> WandererApp.MapSystemRepo.cleanup_tags!()
|
|
|> WandererApp.MapSystemRepo.cleanup_temporary_name!()
|
|
|> WandererApp.MapSystemRepo.update_visible(%{visible: true})
|
|
end
|
|
|
|
_ ->
|
|
{:ok, solar_system_info} =
|
|
WandererApp.CachedInfo.get_system_static_info(solar_system_id)
|
|
|
|
@ddrt.insert(
|
|
{solar_system_id,
|
|
WandererApp.Map.PositionCalculator.get_system_bounding_rect(%{
|
|
position_x: x,
|
|
position_y: y
|
|
})},
|
|
rtree_name
|
|
)
|
|
|
|
WandererApp.MapSystemRepo.create(%{
|
|
map_id: map_id,
|
|
solar_system_id: solar_system_id,
|
|
name: solar_system_info.solar_system_name,
|
|
position_x: x,
|
|
position_y: y
|
|
})
|
|
end
|
|
|
|
:ok = map_id |> WandererApp.Map.add_system(system)
|
|
|
|
WandererApp.Cache.put(
|
|
"map_#{map_id}:system_#{system.id}:last_activity",
|
|
DateTime.utc_now(),
|
|
ttl: @system_inactive_timeout
|
|
)
|
|
|
|
Impl.broadcast!(map_id, :add_system, system)
|
|
|
|
{:ok, _} =
|
|
WandererApp.User.ActivityTracker.track_map_event(:system_added, %{
|
|
character_id: character_id,
|
|
user_id: user_id,
|
|
map_id: map_id,
|
|
solar_system_id: solar_system_id
|
|
})
|
|
|
|
state
|
|
end
|
|
|
|
defp calc_new_system_position(map_id, old_location, rtree_name, opts),
|
|
do:
|
|
{:ok,
|
|
map_id
|
|
|> WandererApp.Map.find_system_by_location(old_location)
|
|
|> WandererApp.Map.PositionCalculator.get_new_system_position(rtree_name, opts)}
|
|
|
|
defp update_system(
|
|
%{map_id: map_id} = state,
|
|
update_method,
|
|
attributes,
|
|
update,
|
|
callback_fn \\ nil
|
|
) do
|
|
with :ok <- WandererApp.Map.update_system_by_solar_system_id(map_id, update),
|
|
{:ok, system} <-
|
|
WandererApp.MapSystemRepo.get_by_map_and_solar_system_id(
|
|
map_id,
|
|
update.solar_system_id
|
|
),
|
|
{:ok, update_map} <- Impl.get_update_map(update, attributes) do
|
|
{:ok, updated_system} =
|
|
apply(WandererApp.MapSystemRepo, update_method, [
|
|
system,
|
|
update_map
|
|
])
|
|
|
|
if not is_nil(callback_fn) do
|
|
callback_fn.(updated_system)
|
|
end
|
|
|
|
update_map_system_last_activity(map_id, updated_system)
|
|
|
|
state
|
|
else
|
|
error ->
|
|
Logger.error("Failed to update system: #{inspect(error, pretty: true)}")
|
|
state
|
|
end
|
|
end
|
|
|
|
defp update_map_system_last_activity(
|
|
map_id,
|
|
updated_system
|
|
) do
|
|
WandererApp.Cache.put(
|
|
"map_#{map_id}:system_#{updated_system.id}:last_activity",
|
|
DateTime.utc_now(),
|
|
ttl: @system_inactive_timeout
|
|
)
|
|
|
|
Impl.broadcast!(map_id, :update_system, updated_system)
|
|
end
|
|
end
|