From f1d6aad8f9d0e131a5ef007ed387ca03f25197e3 Mon Sep 17 00:00:00 2001 From: unknown Date: Sun, 24 May 2026 23:38:43 +0200 Subject: [PATCH 1/5] Feat: PlayerPositionSavingOnLogout --- Server/Components/Pages/GamePage.razor | 7 +++ Server/Components/Pages/GamePage.razor.cs | 58 ++++++++++++++++++- .../FractureDbContextModelSnapshot.cs | 3 + Server/Modules/Database/IUsersRepository.cs | 1 + Server/Modules/Database/UsersRepository.cs | 5 ++ Server/Modules/Users/Models/User.cs | 5 ++ .../Modules/Users/Services/MovementService.cs | 10 +++- Server/Modules/Users/Services/UserService.cs | 18 +++++- 8 files changed, 101 insertions(+), 6 deletions(-) diff --git a/Server/Components/Pages/GamePage.razor b/Server/Components/Pages/GamePage.razor index fae5361..a01562c 100644 --- a/Server/Components/Pages/GamePage.razor +++ b/Server/Components/Pages/GamePage.razor @@ -153,3 +153,10 @@ + +@code { + private async Task Logout() + { + await LogoutAsync(); + } +} diff --git a/Server/Components/Pages/GamePage.razor.cs b/Server/Components/Pages/GamePage.razor.cs index 0b550e6..21e991a 100644 --- a/Server/Components/Pages/GamePage.razor.cs +++ b/Server/Components/Pages/GamePage.razor.cs @@ -9,7 +9,7 @@ namespace Fracture.Server.Components.Pages; -public partial class GamePage +public partial class GamePage : IAsyncDisposable { private Dictionary _mapPopupParameters = null!; @@ -33,6 +33,12 @@ protected override async Task OnInitializedAsync() { await MovementService.InitializeAsync(); BackgroundImage = GetBackgroundImagePath(); + + // Załaduj zapisaną pozycję gracza (jeśli istnieje) + if (UserService.User != null) + { + await LoadPlayerPositionAsync(); + } } MovementService.OnMapEntered += async (sender, args) => @@ -110,10 +116,56 @@ private async Task LoadUserAsync() return true; } - private void Logout() + private async Task LoadPlayerPositionAsync() + { + if (UserService.User == null) + return; + + var positionString = await UserService.GetPlayerPositionAsync(UserService.User.Id); + + if (string.IsNullOrEmpty(positionString)) + return; + + var parts = positionString.Split(','); + if ( + parts.Length == 2 + && int.TryParse(parts[0], out int x) + && int.TryParse(parts[1], out int y) + ) + { + if (MovementService.CanMove(x, y)) + { + MovementService.CurrentX = x; + MovementService.CurrentY = y; + Logger.LogInformation($"Loaded player position: ({x}, {y})"); + } + } + } + + private async Task SavePlayerPositionAsync() + { + if (UserService.User != null && MovementService.CurrentMap != null) + { + var position = $"{MovementService.CurrentX},{MovementService.CurrentY}"; + await UserService.UpdatePlayerPositionAsync(UserService.User.Id, position); + Logger.LogInformation($"Saved player position: {position}"); + } + } + + private async Task LogoutAsync() { + // Zapisz pozycję przed wylogowaniem + await SavePlayerPositionAsync(); + + await ProtectedSessionStore.DeleteAsync("username"); NavigationManager.NavigateTo("/home"); - ProtectedSessionStore.DeleteAsync("username"); + } + + public async ValueTask DisposeAsync() + { + // Zapisz pozycję gdy gracz zamknie przeglądarkę + await SavePlayerPositionAsync(); + GC.SuppressFinalize(this); } private string GetBackgroundImagePath() diff --git a/Server/Migrations/FractureDbContextModelSnapshot.cs b/Server/Migrations/FractureDbContextModelSnapshot.cs index 560be54..37c031a 100644 --- a/Server/Migrations/FractureDbContextModelSnapshot.cs +++ b/Server/Migrations/FractureDbContextModelSnapshot.cs @@ -114,6 +114,9 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("CreatedAt") .HasColumnType("TEXT"); + b.Property("PlayerPosition") + .HasColumnType("TEXT"); + b.Property("Username") .IsRequired() .HasColumnType("TEXT"); diff --git a/Server/Modules/Database/IUsersRepository.cs b/Server/Modules/Database/IUsersRepository.cs index c528ca6..0e43c2b 100644 --- a/Server/Modules/Database/IUsersRepository.cs +++ b/Server/Modules/Database/IUsersRepository.cs @@ -7,4 +7,5 @@ public interface IUsersRepository public Task AddUserAsync(User user); public Task GetUserAsync(int id); public Task GetUserAsync(string username); + public Task SaveAsync(); } diff --git a/Server/Modules/Database/UsersRepository.cs b/Server/Modules/Database/UsersRepository.cs index 7bc2104..0425b2b 100644 --- a/Server/Modules/Database/UsersRepository.cs +++ b/Server/Modules/Database/UsersRepository.cs @@ -38,4 +38,9 @@ public async Task AddUserAsync(User user) .FirstOrDefaultAsync(); return user; } + + public async Task SaveAsync() + { + await _dbContext.SaveChangesAsync(); + } } diff --git a/Server/Modules/Users/Models/User.cs b/Server/Modules/Users/Models/User.cs index 11df551..69b8e40 100644 --- a/Server/Modules/Users/Models/User.cs +++ b/Server/Modules/Users/Models/User.cs @@ -13,6 +13,11 @@ public class User public required string Username { get; set; } + /// + /// Pozycja gracza w formacie "X,Y" + /// + public string? PlayerPosition { get; set; } + public DateTime CreatedAt { get; set; } = DateTime.UtcNow; [InverseProperty(nameof(Item.CreatedBy))] diff --git a/Server/Modules/Users/Services/MovementService.cs b/Server/Modules/Users/Services/MovementService.cs index 971d9ce..1aa5103 100644 --- a/Server/Modules/Users/Services/MovementService.cs +++ b/Server/Modules/Users/Services/MovementService.cs @@ -35,8 +35,8 @@ ItemDropStateService itemDropState public Map? CurrentMap { get; private set; } - public int CurrentX { get; private set; } - public int CurrentY { get; private set; } + public int CurrentX { get; set; } + public int CurrentY { get; set; } public event EventHandler? OnMoved; public event EventHandler<(Map, Position)>? OnMapEntered; @@ -149,4 +149,10 @@ await _itemDropState.TryCollectAsync( position.Y ); } + + public async Task SavePlayerPositionAsync(int userId) + { + var position = $"{CurrentX},{CurrentY}"; + await _userService.UpdatePlayerPositionAsync(userId, position); + } } diff --git a/Server/Modules/Users/Services/UserService.cs b/Server/Modules/Users/Services/UserService.cs index ae0e9d2..76f87f5 100644 --- a/Server/Modules/Users/Services/UserService.cs +++ b/Server/Modules/Users/Services/UserService.cs @@ -6,7 +6,7 @@ namespace Fracture.Server.Modules.Users.Services; -public class UserService(IItemsRepository _itemsRepository) +public class UserService(IItemsRepository _itemsRepository, IUsersRepository _usersRepository) { public User? User { get; private set; } @@ -77,4 +77,20 @@ public bool IsEquipped(Item item) { return Equipment.Contains(item); } + + public async Task UpdatePlayerPositionAsync(int userId, string position) + { + var user = await _usersRepository.GetUserAsync(userId); // ← zmienić z GetUserByIdAsync + if (user != null) + { + user.PlayerPosition = position; + await _usersRepository.SaveAsync(); + } + } + + public async Task GetPlayerPositionAsync(int userId) + { + var user = await _usersRepository.GetUserAsync(userId); // ← zmienić z GetUserByIdAsync + return user?.PlayerPosition; + } } From daeee30ce8af83d603e4e4e82a28baeeb5addb75 Mon Sep 17 00:00:00 2001 From: unknown Date: Mon, 25 May 2026 00:19:36 +0200 Subject: [PATCH 2/5] fix:SpawningOnlyOnWalkableNods --- Server/Components/Pages/GamePage.razor.cs | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/Server/Components/Pages/GamePage.razor.cs b/Server/Components/Pages/GamePage.razor.cs index 21e991a..86ba32b 100644 --- a/Server/Components/Pages/GamePage.razor.cs +++ b/Server/Components/Pages/GamePage.razor.cs @@ -118,7 +118,7 @@ private async Task LoadUserAsync() private async Task LoadPlayerPositionAsync() { - if (UserService.User == null) + if (UserService.User == null || MovementService.CurrentMap == null) return; var positionString = await UserService.GetPlayerPositionAsync(UserService.User.Id); @@ -133,12 +133,24 @@ private async Task LoadPlayerPositionAsync() && int.TryParse(parts[1], out int y) ) { + // Sprawdzenie czy pozycja jest walkable na aktualnej mapie if (MovementService.CanMove(x, y)) { MovementService.CurrentX = x; MovementService.CurrentY = y; Logger.LogInformation($"Loaded player position: ({x}, {y})"); } + else + { + // Pozycja nie jest walkable, losujemy nową + var randomPos = MovementService.CurrentMap.GetRandomWalkableNode(); + MovementService.CurrentX = randomPos.X; + MovementService.CurrentY = randomPos.Y; + await SavePlayerPositionAsync(); + Logger.LogWarning( + $"Saved position ({x}, {y}) is not walkable on current map. Spawned at random position: ({randomPos.X}, {randomPos.Y})" + ); + } } } From 648498bcb8744bb1f2d6d8f6231b5a716b3ecb21 Mon Sep 17 00:00:00 2001 From: unknown Date: Tue, 26 May 2026 20:31:52 +0200 Subject: [PATCH 3/5] feat: PostionAutoSave --- Server/Components/Pages/GamePage.razor.cs | 69 ++++++++++++++++++++--- 1 file changed, 60 insertions(+), 9 deletions(-) diff --git a/Server/Components/Pages/GamePage.razor.cs b/Server/Components/Pages/GamePage.razor.cs index 86ba32b..0fcc9f6 100644 --- a/Server/Components/Pages/GamePage.razor.cs +++ b/Server/Components/Pages/GamePage.razor.cs @@ -12,14 +12,16 @@ namespace Fracture.Server.Components.Pages; public partial class GamePage : IAsyncDisposable { private Dictionary _mapPopupParameters = null!; - private PopupContainer _popup = null!; - public string BackgroundImage { get; set; } = string.Empty; - private readonly MapDisplayOptions _mapDisplayOptions = new(); - private List? Path { get; set; } + private CancellationTokenSource _autosaveCts = null!; + + // Zmienne do debounce'a i auto-save'a + private int _lastSavedX = -1; + private int _lastSavedY = -1; // 3 sekundy + private const int AUTOSAVE_INTERVAL_MS = 120000; // 2 minuty protected override async Task OnInitializedAsync() { @@ -89,6 +91,9 @@ protected override async Task OnInitializedAsync() { "MapDisplayData", _mapDisplayOptions }, }; + _autosaveCts = new CancellationTokenSource(); + _ = AutosavePositionAsync(_autosaveCts.Token); + await base.OnInitializedAsync(); } @@ -133,22 +138,29 @@ private async Task LoadPlayerPositionAsync() && int.TryParse(parts[1], out int y) ) { - // Sprawdzenie czy pozycja jest walkable na aktualnej mapie if (MovementService.CanMove(x, y)) { + // Pozycja jest walkable - załaduj bez problemu MovementService.CurrentX = x; MovementService.CurrentY = y; + _lastSavedX = x; + _lastSavedY = y; Logger.LogInformation($"Loaded player position: ({x}, {y})"); } else { - // Pozycja nie jest walkable, losujemy nową + // Pozycja nie jest walkable - losuj nową i od razu zapisz do bazy var randomPos = MovementService.CurrentMap.GetRandomWalkableNode(); MovementService.CurrentX = randomPos.X; MovementService.CurrentY = randomPos.Y; + _lastSavedX = randomPos.X; + _lastSavedY = randomPos.Y; + + // Natychmiast zapisz nową pozycję do bazy await SavePlayerPositionAsync(); + Logger.LogWarning( - $"Saved position ({x}, {y}) is not walkable on current map. Spawned at random position: ({randomPos.X}, {randomPos.Y})" + $"Saved position ({x}, {y}) is not walkable on current map. Spawned at random position: ({randomPos.X}, {randomPos.Y}) - position saved immediately" ); } } @@ -164,9 +176,45 @@ private async Task SavePlayerPositionAsync() } } + private async Task AutosavePositionAsync(CancellationToken cancellationToken) + { + while (!cancellationToken.IsCancellationRequested) + { + try + { + await Task.Delay(AUTOSAVE_INTERVAL_MS, cancellationToken); // Co 2 minuty + + // Zapisz tylko jeśli pozycja się zmieniła + if ( + MovementService.CurrentX != _lastSavedX + || MovementService.CurrentY != _lastSavedY + ) + { + await SavePlayerPositionAsync(); + _lastSavedX = MovementService.CurrentX; + _lastSavedY = MovementService.CurrentY; + Logger.LogInformation( + $"Autosave triggered - position changed: ({_lastSavedX}, {_lastSavedY})" + ); + } + else + { + Logger.LogInformation("Autosave skipped - position unchanged"); + } + } + catch (OperationCanceledException) + { + break; + } + catch (Exception ex) + { + Logger.LogError(ex, "Error during autosave"); + } + } + } + private async Task LogoutAsync() { - // Zapisz pozycję przed wylogowaniem await SavePlayerPositionAsync(); await ProtectedSessionStore.DeleteAsync("username"); @@ -175,8 +223,11 @@ private async Task LogoutAsync() public async ValueTask DisposeAsync() { - // Zapisz pozycję gdy gracz zamknie przeglądarkę + _autosaveCts?.Cancel(); + _autosaveCts?.Dispose(); + await SavePlayerPositionAsync(); + GC.SuppressFinalize(this); } From 7826e1685ae962a5b3d70d6121fa898ce6359791 Mon Sep 17 00:00:00 2001 From: unknown Date: Tue, 26 May 2026 20:43:07 +0200 Subject: [PATCH 4/5] refact: CodeCleaning --- Server/Components/Pages/GamePage.razor.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Server/Components/Pages/GamePage.razor.cs b/Server/Components/Pages/GamePage.razor.cs index 0fcc9f6..00a984c 100644 --- a/Server/Components/Pages/GamePage.razor.cs +++ b/Server/Components/Pages/GamePage.razor.cs @@ -18,9 +18,9 @@ public partial class GamePage : IAsyncDisposable private List? Path { get; set; } private CancellationTokenSource _autosaveCts = null!; - // Zmienne do debounce'a i auto-save'a - private int _lastSavedX = -1; - private int _lastSavedY = -1; // 3 sekundy + // Zmienne do auto-save'a + private int _lastSavedX; + private int _lastSavedY; private const int AUTOSAVE_INTERVAL_MS = 120000; // 2 minuty protected override async Task OnInitializedAsync() From 553b5bc4a6375b645a1fb12b6f5ab33f2a721188 Mon Sep 17 00:00:00 2001 From: unknown Date: Wed, 27 May 2026 23:24:45 +0200 Subject: [PATCH 5/5] refact: MovedLogicToMovementService --- Server/Components/Pages/GamePage.razor.cs | 126 ++-------------- .../Modules/Users/Services/MovementService.cs | 139 ++++++++++++++++-- Server/Modules/Users/Services/UserService.cs | 4 +- 3 files changed, 137 insertions(+), 132 deletions(-) diff --git a/Server/Components/Pages/GamePage.razor.cs b/Server/Components/Pages/GamePage.razor.cs index 00a984c..de5ffa6 100644 --- a/Server/Components/Pages/GamePage.razor.cs +++ b/Server/Components/Pages/GamePage.razor.cs @@ -1,6 +1,5 @@ using Fracture.Server.Components.Popups; using Fracture.Server.Components.UI; -using Fracture.Server.Modules.Items.Models; using Fracture.Server.Modules.MapGenerator.Models.Map; using Fracture.Server.Modules.MapGenerator.UI.Models; using Fracture.Server.Modules.Pathfinding.Models; @@ -16,12 +15,6 @@ public partial class GamePage : IAsyncDisposable public string BackgroundImage { get; set; } = string.Empty; private readonly MapDisplayOptions _mapDisplayOptions = new(); private List? Path { get; set; } - private CancellationTokenSource _autosaveCts = null!; - - // Zmienne do auto-save'a - private int _lastSavedX; - private int _lastSavedY; - private const int AUTOSAVE_INTERVAL_MS = 120000; // 2 minuty protected override async Task OnInitializedAsync() { @@ -35,12 +28,12 @@ protected override async Task OnInitializedAsync() { await MovementService.InitializeAsync(); BackgroundImage = GetBackgroundImagePath(); - - // Załaduj zapisaną pozycję gracza (jeśli istnieje) - if (UserService.User != null) - { - await LoadPlayerPositionAsync(); - } + } + else + { + //Reset dla nowego gracza + await MovementService.ReloadPlayerPositionAsync(); + BackgroundImage = GetBackgroundImagePath(); } MovementService.OnMapEntered += async (sender, args) => @@ -90,9 +83,8 @@ protected override async Task OnInitializedAsync() { { "MapDisplayData", _mapDisplayOptions }, }; - - _autosaveCts = new CancellationTokenSource(); - _ = AutosavePositionAsync(_autosaveCts.Token); + //Autosave Pozycji Gracza + MovementService.StartAutosave(); await base.OnInitializedAsync(); } @@ -121,113 +113,17 @@ private async Task LoadUserAsync() return true; } - private async Task LoadPlayerPositionAsync() - { - if (UserService.User == null || MovementService.CurrentMap == null) - return; - - var positionString = await UserService.GetPlayerPositionAsync(UserService.User.Id); - - if (string.IsNullOrEmpty(positionString)) - return; - - var parts = positionString.Split(','); - if ( - parts.Length == 2 - && int.TryParse(parts[0], out int x) - && int.TryParse(parts[1], out int y) - ) - { - if (MovementService.CanMove(x, y)) - { - // Pozycja jest walkable - załaduj bez problemu - MovementService.CurrentX = x; - MovementService.CurrentY = y; - _lastSavedX = x; - _lastSavedY = y; - Logger.LogInformation($"Loaded player position: ({x}, {y})"); - } - else - { - // Pozycja nie jest walkable - losuj nową i od razu zapisz do bazy - var randomPos = MovementService.CurrentMap.GetRandomWalkableNode(); - MovementService.CurrentX = randomPos.X; - MovementService.CurrentY = randomPos.Y; - _lastSavedX = randomPos.X; - _lastSavedY = randomPos.Y; - - // Natychmiast zapisz nową pozycję do bazy - await SavePlayerPositionAsync(); - - Logger.LogWarning( - $"Saved position ({x}, {y}) is not walkable on current map. Spawned at random position: ({randomPos.X}, {randomPos.Y}) - position saved immediately" - ); - } - } - } - - private async Task SavePlayerPositionAsync() - { - if (UserService.User != null && MovementService.CurrentMap != null) - { - var position = $"{MovementService.CurrentX},{MovementService.CurrentY}"; - await UserService.UpdatePlayerPositionAsync(UserService.User.Id, position); - Logger.LogInformation($"Saved player position: {position}"); - } - } - - private async Task AutosavePositionAsync(CancellationToken cancellationToken) - { - while (!cancellationToken.IsCancellationRequested) - { - try - { - await Task.Delay(AUTOSAVE_INTERVAL_MS, cancellationToken); // Co 2 minuty - - // Zapisz tylko jeśli pozycja się zmieniła - if ( - MovementService.CurrentX != _lastSavedX - || MovementService.CurrentY != _lastSavedY - ) - { - await SavePlayerPositionAsync(); - _lastSavedX = MovementService.CurrentX; - _lastSavedY = MovementService.CurrentY; - Logger.LogInformation( - $"Autosave triggered - position changed: ({_lastSavedX}, {_lastSavedY})" - ); - } - else - { - Logger.LogInformation("Autosave skipped - position unchanged"); - } - } - catch (OperationCanceledException) - { - break; - } - catch (Exception ex) - { - Logger.LogError(ex, "Error during autosave"); - } - } - } - private async Task LogoutAsync() { - await SavePlayerPositionAsync(); - + await MovementService.SavePlayerPositionAsync(); await ProtectedSessionStore.DeleteAsync("username"); NavigationManager.NavigateTo("/home"); } public async ValueTask DisposeAsync() { - _autosaveCts?.Cancel(); - _autosaveCts?.Dispose(); - - await SavePlayerPositionAsync(); - + MovementService.StopAutosave(); + await MovementService.SavePlayerPositionAsync(); GC.SuppressFinalize(this); } diff --git a/Server/Modules/Users/Services/MovementService.cs b/Server/Modules/Users/Services/MovementService.cs index 1aa5103..2556cf3 100644 --- a/Server/Modules/Users/Services/MovementService.cs +++ b/Server/Modules/Users/Services/MovementService.cs @@ -4,6 +4,7 @@ using Fracture.Server.Modules.Items.Services; using Fracture.Server.Modules.MapGenerator.Models.Map; using Fracture.Server.Modules.MapGenerator.Services; +using Microsoft.Extensions.Logging; namespace Fracture.Server.Modules.Users.Services; @@ -14,16 +15,26 @@ public class MovementService private readonly IItemsRepository _itemsRepository; private readonly UserService _userService; private readonly ItemDropStateService _itemDropState; + private readonly IUsersRepository _usersRepository; + private readonly ILogger _logger; private Position? _pendingPickupPosition; private Item? _pendingPickupItem; + // Autosave fields + private CancellationTokenSource? _autosaveCts; + private int _lastSavedX; + private int _lastSavedY; + private const int AUTOSAVE_INTERVAL_MS = 120000; // 2 minuty + public MovementService( MapManagerService mapManagerService, IItemGenerator itemGenerator, IItemsRepository itemsRepository, UserService userService, - ItemDropStateService itemDropState + ItemDropStateService itemDropState, + IUsersRepository usersRepository, + ILogger logger ) { _mapManagerService = mapManagerService; @@ -31,12 +42,14 @@ ItemDropStateService itemDropState _itemsRepository = itemsRepository; _userService = userService; _itemDropState = itemDropState; + _usersRepository = usersRepository; + _logger = logger; } public Map? CurrentMap { get; private set; } - public int CurrentX { get; set; } - public int CurrentY { get; set; } + public int CurrentX { get; private set; } + public int CurrentY { get; private set; } public event EventHandler? OnMoved; public event EventHandler<(Map, Position)>? OnMapEntered; @@ -49,16 +62,118 @@ public async Task InitializeAsync() _mapManagerService.GetWorldMap() ?? throw new InvalidOperationException("Map cannot be loaded, critical error"); - var start = CurrentMap.GetRandomWalkableNode(); - CurrentX = start.X; - CurrentY = start.Y; + // Wczytaj zapisaną pozycję gracza + await LoadPlayerPositionAsync(); + + OnMapEntered?.Invoke(this, (CurrentMap, new Position(CurrentX, CurrentY))); + + // Uruchom autosave automatycznie + StartAutosave(); + } + + /// + /// Wczytuje pozycję gracza z bazy danych + /// + public async Task LoadPlayerPositionAsync() + { + if (_userService.User?.PlayerPosition is null) + { + // Jeśli nie ma zapisanej pozycji, ustaw pozycję startową + CurrentX = CurrentMap?.GetRandomWalkableNode().X ?? 0; + CurrentY = CurrentMap?.GetRandomWalkableNode().Y ?? 0; + return; + } + + var parts = _userService.User.PlayerPosition.Split(','); + if ( + parts.Length == 2 + && int.TryParse(parts[0], out var x) + && int.TryParse(parts[1], out var y) + ) + { + if (CanMove(x, y)) + { + CurrentX = x; + CurrentY = y; + _lastSavedX = x; + _lastSavedY = y; + _logger.LogInformation($"Loaded player position: ({x}, {y})"); + } + else + { + // Pozycja nie jest walkable - losuj nową + var randomPos = CurrentMap.GetRandomWalkableNode(); + CurrentX = randomPos.X; + CurrentY = randomPos.Y; + _lastSavedX = randomPos.X; + _lastSavedY = randomPos.Y; + } + } + } + + /// + /// Zapisuje aktualną pozycję gracza do bazy danych + /// + public async Task SavePlayerPositionAsync() + { + if (_userService.User is null) + return; + + _userService.User.PlayerPosition = $"{CurrentX},{CurrentY}"; + await _usersRepository.SaveAsync(); + } + + /// + /// Uruchamia autosave pozycji gracza co 2 minuty + /// + public void StartAutosave() + { + _autosaveCts = new CancellationTokenSource(); + _ = AutosavePositionAsync(_autosaveCts.Token); + } + + /// + /// Zatrzymuje autosave + /// + public void StopAutosave() + { + _autosaveCts?.Cancel(); + _autosaveCts?.Dispose(); + _autosaveCts = null; + } - if (_userService.User != null) + /// + /// Pętla autosave'a + /// + private async Task AutosavePositionAsync(CancellationToken cancellationToken) + { + while (!cancellationToken.IsCancellationRequested) { - await _itemDropState.EnsureLoadedAsync(_userService.User.Id); + await Task.Delay(AUTOSAVE_INTERVAL_MS, cancellationToken); + if (CurrentX != _lastSavedX || CurrentY != _lastSavedY) + { + await SavePlayerPositionAsync(); + _lastSavedX = CurrentX; + _lastSavedY = CurrentY; + _logger.LogInformation( + $"Autosave triggered - position changed: ({_lastSavedX}, {_lastSavedY})" + ); + } + else + { + _logger.LogInformation("Autosave skipped - position unchanged"); + } } + } - OnMapEntered?.Invoke(this, new(CurrentMap, new Position(CurrentX, CurrentY))); + /// + /// Przeładowuje pozycję gracza (dla nowego użytkownika) + /// + public async Task ReloadPlayerPositionAsync() + { + _lastSavedX = CurrentX; + _lastSavedY = CurrentY; + await LoadPlayerPositionAsync(); } public bool CanMove(int x, int y) @@ -149,10 +264,4 @@ await _itemDropState.TryCollectAsync( position.Y ); } - - public async Task SavePlayerPositionAsync(int userId) - { - var position = $"{CurrentX},{CurrentY}"; - await _userService.UpdatePlayerPositionAsync(userId, position); - } } diff --git a/Server/Modules/Users/Services/UserService.cs b/Server/Modules/Users/Services/UserService.cs index 76f87f5..ec31838 100644 --- a/Server/Modules/Users/Services/UserService.cs +++ b/Server/Modules/Users/Services/UserService.cs @@ -80,7 +80,7 @@ public bool IsEquipped(Item item) public async Task UpdatePlayerPositionAsync(int userId, string position) { - var user = await _usersRepository.GetUserAsync(userId); // ← zmienić z GetUserByIdAsync + var user = await _usersRepository.GetUserAsync(userId); if (user != null) { user.PlayerPosition = position; @@ -90,7 +90,7 @@ public async Task UpdatePlayerPositionAsync(int userId, string position) public async Task GetPlayerPositionAsync(int userId) { - var user = await _usersRepository.GetUserAsync(userId); // ← zmienić z GetUserByIdAsync + var user = await _usersRepository.GetUserAsync(userId); return user?.PlayerPosition; } }