Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions Server/Components/Pages/GamePage.razor
Original file line number Diff line number Diff line change
Expand Up @@ -153,3 +153,10 @@
</div>
</div>
</div>

@code {
private async Task Logout()
{
await LogoutAsync();
}
}
27 changes: 19 additions & 8 deletions Server/Components/Pages/GamePage.razor.cs
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -9,16 +8,12 @@

namespace Fracture.Server.Components.Pages;

public partial class GamePage
public partial class GamePage : IAsyncDisposable
{
private Dictionary<string, object> _mapPopupParameters = null!;

private PopupContainer _popup = null!;

public string BackgroundImage { get; set; } = string.Empty;

private readonly MapDisplayOptions _mapDisplayOptions = new();

private List<IPathfindingNode>? Path { get; set; }

protected override async Task OnInitializedAsync()
Expand All @@ -34,6 +29,12 @@ protected override async Task OnInitializedAsync()
await MovementService.InitializeAsync();
BackgroundImage = GetBackgroundImagePath();
}
else
{
//Reset dla nowego gracza
await MovementService.ReloadPlayerPositionAsync();
BackgroundImage = GetBackgroundImagePath();
}

MovementService.OnMapEntered += async (sender, args) =>
{
Expand Down Expand Up @@ -82,6 +83,8 @@ protected override async Task OnInitializedAsync()
{
{ "MapDisplayData", _mapDisplayOptions },
};
//Autosave Pozycji Gracza
MovementService.StartAutosave();

await base.OnInitializedAsync();
}
Expand Down Expand Up @@ -110,10 +113,18 @@ private async Task<bool> LoadUserAsync()
return true;
}

private void Logout()
private async Task LogoutAsync()
{
await MovementService.SavePlayerPositionAsync();
await ProtectedSessionStore.DeleteAsync("username");
NavigationManager.NavigateTo("/home");
ProtectedSessionStore.DeleteAsync("username");
}

public async ValueTask DisposeAsync()
{
MovementService.StopAutosave();
await MovementService.SavePlayerPositionAsync();
GC.SuppressFinalize(this);
}

private string GetBackgroundImagePath()
Expand Down
3 changes: 3 additions & 0 deletions Server/Migrations/FractureDbContextModelSnapshot.cs
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,9 @@ protected override void BuildModel(ModelBuilder modelBuilder)
b.Property<DateTime>("CreatedAt")
.HasColumnType("TEXT");

b.Property<string>("PlayerPosition")
.HasColumnType("TEXT");

b.Property<string>("Username")
.IsRequired()
.HasColumnType("TEXT");
Expand Down
6 changes: 6 additions & 0 deletions Server/Migrations/ItemsDropped.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,12 @@ public partial class ItemsDropped : Migration
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<string>(
name: "PlayerPosition",
table: "Users",
type: "TEXT",
nullable: true);

migrationBuilder.DropForeignKey(
name: "FK_Items_Users_CreatedById",
table: "Items");
Expand Down
1 change: 1 addition & 0 deletions Server/Modules/Database/IUsersRepository.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,5 @@ public interface IUsersRepository
public Task<User> AddUserAsync(User user);
public Task<User?> GetUserAsync(int id);
public Task<User?> GetUserAsync(string username);
public Task SaveAsync();
}
5 changes: 5 additions & 0 deletions Server/Modules/Database/UsersRepository.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,4 +38,9 @@ public async Task<User> AddUserAsync(User user)
.FirstOrDefaultAsync();
return user;
}

public async Task SaveAsync()
{
await _dbContext.SaveChangesAsync();
}
}
5 changes: 5 additions & 0 deletions Server/Modules/Users/Models/User.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,11 @@ public class User

public required string Username { get; set; }

/// <summary>
/// Pozycja gracza w formacie "X,Y"
/// </summary>
public string? PlayerPosition { get; set; }

public DateTime CreatedAt { get; set; } = DateTime.UtcNow;

[InverseProperty(nameof(Item.CreatedBy))]
Expand Down
129 changes: 122 additions & 7 deletions Server/Modules/Users/Services/MovementService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -14,23 +15,35 @@
private readonly IItemsRepository _itemsRepository;
private readonly UserService _userService;
private readonly ItemDropStateService _itemDropState;
private readonly IUsersRepository _usersRepository;
private readonly ILogger<MovementService> _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<MovementService> logger
)
{
_mapManagerService = mapManagerService;
_itemGenerator = itemGenerator;
_itemsRepository = itemsRepository;
_userService = userService;
_itemDropState = itemDropState;
_usersRepository = usersRepository;
_logger = logger;
}

public Map? CurrentMap { get; private set; }
Expand All @@ -49,16 +62,118 @@
_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)));

if (_userService.User != null)
// Uruchom autosave automatycznie
StartAutosave();
}

/// <summary>
/// Wczytuje pozycję gracza z bazy danych
/// </summary>
public async Task LoadPlayerPositionAsync()
{
if (_userService.User?.PlayerPosition is null)
{
await _itemDropState.EnsureLoadedAsync(_userService.User.Id);
// Jeśli nie ma zapisanej pozycji, ustaw pozycję startową
CurrentX = CurrentMap?.GetRandomWalkableNode().X ?? 0;
CurrentY = CurrentMap?.GetRandomWalkableNode().Y ?? 0;
return;
}

OnMapEntered?.Invoke(this, new(CurrentMap, new Position(CurrentX, CurrentY)));
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();

Check warning on line 105 in Server/Modules/Users/Services/MovementService.cs

View workflow job for this annotation

GitHub Actions / Checks

Dereference of a possibly null reference.

Check warning on line 105 in Server/Modules/Users/Services/MovementService.cs

View workflow job for this annotation

GitHub Actions / Checks

Dereference of a possibly null reference.
CurrentX = randomPos.X;
CurrentY = randomPos.Y;
_lastSavedX = randomPos.X;
_lastSavedY = randomPos.Y;
}
}
}

/// <summary>
/// Zapisuje aktualną pozycję gracza do bazy danych
/// </summary>
public async Task SavePlayerPositionAsync()
{
if (_userService.User is null)
return;

_userService.User.PlayerPosition = $"{CurrentX},{CurrentY}";
await _usersRepository.SaveAsync();
}

/// <summary>
/// Uruchamia autosave pozycji gracza co 2 minuty
/// </summary>
public void StartAutosave()
{
_autosaveCts = new CancellationTokenSource();
_ = AutosavePositionAsync(_autosaveCts.Token);
}

/// <summary>
/// Zatrzymuje autosave
/// </summary>
public void StopAutosave()
{
_autosaveCts?.Cancel();
_autosaveCts?.Dispose();
_autosaveCts = null;
}

/// <summary>
/// Pętla autosave'a
/// </summary>
private async Task AutosavePositionAsync(CancellationToken cancellationToken)
{
while (!cancellationToken.IsCancellationRequested)
{
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");
}
}
}

/// <summary>
/// Przeładowuje pozycję gracza (dla nowego użytkownika)
/// </summary>
public async Task ReloadPlayerPositionAsync()
{
_lastSavedX = CurrentX;
_lastSavedY = CurrentY;
await LoadPlayerPositionAsync();
}

public bool CanMove(int x, int y)
Expand Down
18 changes: 17 additions & 1 deletion Server/Modules/Users/Services/UserService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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; }

Expand Down Expand Up @@ -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);
if (user != null)
{
user.PlayerPosition = position;
await _usersRepository.SaveAsync();
}
}

public async Task<string?> GetPlayerPositionAsync(int userId)
{
var user = await _usersRepository.GetUserAsync(userId);
return user?.PlayerPosition;
}
}
Loading