OnlineGameBackend.cs
1 using System.Net.Http.Json; 2 using System.Text.Json; 3 using GUNRPG.Application.Backend; 4 using GUNRPG.Infrastructure.Persistence; 5 6 namespace GUNRPG.Infrastructure.Backend; 7 8 /// <summary> 9 /// Online game backend that delegates to the HTTP API. 10 /// Also handles infil snapshot persistence to local storage. 11 /// Combat remains interactive and player-driven via the session-based API — this backend 12 /// handles operator data access, not gameplay execution. 13 /// </summary> 14 public sealed class OnlineGameBackend : IGameBackend 15 { 16 private readonly HttpClient _httpClient; 17 private readonly OfflineStore _offlineStore; 18 private readonly JsonSerializerOptions _jsonOptions; 19 20 public OnlineGameBackend(HttpClient httpClient, OfflineStore offlineStore, JsonSerializerOptions? jsonOptions = null) 21 { 22 _httpClient = httpClient; 23 _offlineStore = offlineStore; 24 _jsonOptions = jsonOptions ?? new JsonSerializerOptions(JsonSerializerDefaults.Web); 25 } 26 27 /// <inheritdoc /> 28 public async Task<OperatorDto?> GetOperatorAsync(string id) 29 { 30 var response = await _httpClient.GetAsync($"operators/{id}"); 31 if (!response.IsSuccessStatusCode) 32 return null; 33 34 var json = await response.Content.ReadAsStringAsync(); 35 return MapFromApiJson(id, json); 36 } 37 38 /// <inheritdoc /> 39 public async Task<OperatorDto> InfilOperatorAsync(string id) 40 { 41 // Fetch operator from server 42 var response = await _httpClient.GetAsync($"operators/{id}"); 43 if (response.StatusCode == System.Net.HttpStatusCode.NotFound) 44 throw new InvalidOperationException($"Operator {id} not found on server."); 45 response.EnsureSuccessStatusCode(); 46 47 var json = await response.Content.ReadAsStringAsync(); 48 var operatorDto = MapFromApiJson(id, json) 49 ?? throw new InvalidOperationException($"Failed to parse operator {id} response from server."); 50 51 // Persist snapshot locally for offline use 52 _offlineStore.SaveInfiledOperator(operatorDto); 53 54 Console.WriteLine($"[INFIL] Operator '{operatorDto.Name}' (ID: {id}) infiled successfully."); 55 Console.WriteLine($"[INFIL] Snapshot saved — offline play now available if server becomes unreachable."); 56 return operatorDto; 57 } 58 59 /// <inheritdoc /> 60 public async Task<bool> OperatorExistsAsync(string id) 61 { 62 var response = await _httpClient.GetAsync($"operators/{id}"); 63 return response.IsSuccessStatusCode; 64 } 65 66 public async Task<bool> SyncOfflineMission(OfflineMissionEnvelope envelope, CancellationToken cancellationToken = default) 67 { 68 using var response = await _httpClient.PostAsJsonAsync("operators/offline/sync", envelope, _jsonOptions, cancellationToken); 69 return response.IsSuccessStatusCode; 70 } 71 72 /// <summary> 73 /// Maps API JSON response to an OperatorDto. 74 /// </summary> 75 private OperatorDto? MapFromApiJson(string id, string json) 76 { 77 try 78 { 79 var dto = JsonSerializer.Deserialize<OperatorDto>(json, _jsonOptions); 80 if (dto != null) 81 { 82 dto.Id = id; 83 } 84 return dto; 85 } 86 catch 87 { 88 return null; 89 } 90 } 91 }