diff --git a/config/runtime.exs b/config/runtime.exs
index 4ffa7b3..19d63bc 100644
--- a/config/runtime.exs
+++ b/config/runtime.exs
@@ -23,6 +23,9 @@ end
config :cobblemon_ui, CobblemonUiWeb.Endpoint,
http: [port: String.to_integer(System.get_env("PORT", "4000"))]
+config :cobblemon_ui,
+ minecraft_server_url: System.get_env("MINECRAFT_SERVER_URL", "http://localhost:80")
+
if config_env() == :prod do
# The secret key base is used to sign/encrypt cookies and other secrets.
# A default value is used in config/dev.exs and config/test.exs but you
diff --git a/k8s/deployment.yaml b/k8s/deployment.yaml
index f9a1115..747aad8 100644
--- a/k8s/deployment.yaml
+++ b/k8s/deployment.yaml
@@ -28,6 +28,8 @@ spec:
value: "cobblemon.alexmickelson.guru"
- name: PORT
value: "4000"
+ - name: MINECRAFT_SERVER_URL
+ value: "http://minecraft.beefalo-newton.ts.net:8085"
- name: SECRET_KEY_BASE
valueFrom:
secretKeyRef:
diff --git a/lib/cobblemon_ui/battles_api.ex b/lib/cobblemon_ui/battles_api.ex
new file mode 100644
index 0000000..dc50f7c
--- /dev/null
+++ b/lib/cobblemon_ui/battles_api.ex
@@ -0,0 +1,74 @@
+defmodule CobblemonUi.BattlesApi do
+ @moduledoc """
+ Fetches active battle data from the Cobblemon Minecraft server.
+ """
+
+ def server_url do
+ Application.get_env(:cobblemon_ui, :minecraft_server_url, "http://localhost:80")
+ end
+
+ def list_battles do
+ url = "#{server_url()}/battles"
+
+ case Req.get(url) do
+ {:ok, %Req.Response{status: 200, body: body}} ->
+ battles = parse_battles(body)
+ {:ok, battles}
+
+ {:ok, %Req.Response{status: status}} ->
+ {:error, "Server returned status #{status}"}
+
+ {:error, reason} ->
+ {:error, "Request failed: #{inspect(reason)}"}
+ end
+ end
+
+ defp parse_battles(%{"battles" => battles}) when is_list(battles) do
+ Enum.map(battles, &parse_battle/1)
+ end
+
+ defp parse_battles(_), do: []
+
+ defp parse_battle(battle) do
+ %{
+ battle_id: battle["battleId"],
+ actors: Enum.map(battle["actors"] || [], &parse_actor/1)
+ }
+ end
+
+ defp parse_actor(actor) do
+ %{
+ name: actor["name"],
+ type: actor["type"],
+ player_id: actor["playerId"],
+ active_pokemon: Enum.map(actor["activePokemon"] || [], &parse_active_pokemon/1),
+ party: Enum.map(actor["party"] || [], &parse_party_pokemon/1)
+ }
+ end
+
+ defp parse_active_pokemon(p) do
+ %{
+ uuid: p["uuid"],
+ species: p["species"],
+ level: p["level"],
+ hp: p["hp"],
+ max_hp: p["maxHp"]
+ }
+ end
+
+ defp parse_party_pokemon(p) do
+ %{
+ uuid: p["uuid"],
+ species: p["species"],
+ level: p["level"],
+ hp: p["hp"],
+ max_hp: p["maxHp"],
+ ability: p["ability"],
+ nature: p["nature"],
+ shiny: p["shiny"],
+ moves: Enum.map(p["moves"] || [], fn m ->
+ %{name: m["name"], pp: m["pp"], max_pp: m["maxPp"]}
+ end)
+ }
+ end
+end
diff --git a/lib/cobblemon_ui_web/live/battles_live.ex b/lib/cobblemon_ui_web/live/battles_live.ex
new file mode 100644
index 0000000..086c2d8
--- /dev/null
+++ b/lib/cobblemon_ui_web/live/battles_live.ex
@@ -0,0 +1,243 @@
+defmodule CobblemonUiWeb.BattlesLive do
+ use CobblemonUiWeb, :live_view
+
+ @impl true
+ def mount(_params, _session, socket) do
+ {battles, error} =
+ case CobblemonUi.BattlesApi.list_battles() do
+ {:ok, battles} -> {battles, nil}
+ {:error, reason} -> {[], reason}
+ end
+
+ {:ok,
+ assign(socket,
+ page_title: "Active Battles",
+ battles: battles,
+ error: error
+ )}
+ end
+
+ @impl true
+ def handle_event("refresh", _params, socket) do
+ {battles, error} =
+ case CobblemonUi.BattlesApi.list_battles() do
+ {:ok, battles} -> {battles, nil}
+ {:error, reason} -> {[], reason}
+ end
+
+ {:noreply, assign(socket, battles: battles, error: error)}
+ end
+
+ @impl true
+ def render(assigns) do
+ ~H"""
+ Live battle monitor No active battles All is peaceful on the server {actor.name}
+ {actor.type}
+ Active Battles
+
+ <.icon name="hero-chevron-right" class="size-3 group-open:rotate-90 transition-transform" />
+ Party details
+
+
Player Data Explorer
- +