using System; using System.Net; using System.Net.Sockets; using AcDream.Core.Net; namespace AcDream.App.Net; /// /// Owns the network-side lifecycle of a live — /// DNS resolution, endpoint construction, session instantiation, per-frame /// Tick, and disposal. The post-construction work (event wiring, /// Connect, character validation, EnterWorld, post-login UI /// state setup) stays in GameWindow for now because it touches /// renderer / chat / player-controller state that hasn't been extracted yet. /// /// /// /// Step 2 of the extraction sequence described in /// docs/architecture/code-structure.md §4. Future expansions can /// fold more of TryStartLiveSession into this controller as the /// surrounding state (event handlers, command bus, settings VM) gets /// extracted in later steps. /// /// /// Behavior preservation contract: this class produces /// byte-identical console output and event-wireup sequencing to the /// pre-refactor inline code path. The DNS-resolution lines, the /// "live: connecting to ..." line, and the wiring-vs-Connect ordering /// all match the previous flow. /// /// public sealed class LiveSessionController : IDisposable { /// /// Active session, or when offline / before /// succeeded / after . /// public WorldSession? Session { get; private set; } /// /// Resolves the endpoint, instantiates the , /// hands it to for caller-side event /// subscriptions, and returns the live session. The caller is /// responsible for the subsequent Connect / /// EnterWorld dance. /// public WorldSession? CreateAndWire(RuntimeOptions options, Action wireEvents) { if (options is null) throw new ArgumentNullException(nameof(options)); if (wireEvents is null) throw new ArgumentNullException(nameof(wireEvents)); if (!options.LiveMode) return null; if (string.IsNullOrEmpty(options.LiveUser) || string.IsNullOrEmpty(options.LivePass)) { Console.WriteLine("live: ACDREAM_LIVE set but TEST_USER/TEST_PASS missing; skipping"); return null; } try { var endpoint = ResolveEndpoint(options.LiveHost, options.LivePort); Console.WriteLine($"live: connecting to {endpoint} as {options.LiveUser}"); Session = new WorldSession(endpoint); wireEvents(Session); return Session; } catch (Exception ex) { Console.WriteLine($"live: session setup failed: {ex.Message}"); Session?.Dispose(); Session = null; return null; } } /// /// Drains the inbound network queue. Proxies to /// ; no-op when /// is . /// public void Tick() => Session?.Tick(); /// /// Tears down the live session. Safe to call multiple times. /// public void Dispose() { Session?.Dispose(); Session = null; } /// /// Resolve a host string (literal IP or DNS name) to an /// . Pre-refactor logic preserved exactly: /// try first, fall back to /// , prefer IPv4 (ACE + retail use /// IPv4 UDP exclusively), throw on empty resolution. /// private static IPEndPoint ResolveEndpoint(string host, int port) { IPAddress ip; if (!IPAddress.TryParse(host, out ip!)) { var addrs = Dns.GetHostAddresses(host); ip = Array.Find(addrs, a => a.AddressFamily == AddressFamily.InterNetwork) ?? (addrs.Length > 0 ? addrs[0] : throw new Exception($"DNS resolved no addresses for '{host}'")); Console.WriteLine($"live: resolved {host} → {ip}"); } return new IPEndPoint(ip, port); } }