diff --git a/src/Randomizer.Multiplayer.Server/GameManager.cs b/src/Randomizer.Multiplayer.Server/GameManager.cs index 18ae2e07d..52e2fc7d1 100644 --- a/src/Randomizer.Multiplayer.Server/GameManager.cs +++ b/src/Randomizer.Multiplayer.Server/GameManager.cs @@ -54,7 +54,7 @@ private async Task CheckGames() expiredGuids = await _dbService.DeleteOldGameStates(_databaseExpirationDays); _logger.LogInformation("Removed {Amount} inactive games(s) from database", expiredGuids.Count); - _logger.LogInformation("Current active games: {GameCount} | Current connected players: {PlayerCount}", MultiplayerGame.GameCount, MultiplayerGame.PlayerCount); + _logger.LogInformation("[{Date}] Current active games: {GameCount} | Current connected players: {PlayerCount}", DateTime.Now, MultiplayerGame.GameCount, MultiplayerGame.PlayerCount); await Task.Delay(TimeSpan.FromMinutes(_checkFrequencyMinutes)); } diff --git a/src/Randomizer.Multiplayer.Server/Migrations/20230621133443_DeathLink.Designer.cs b/src/Randomizer.Multiplayer.Server/Migrations/20230621133443_DeathLink.Designer.cs new file mode 100644 index 000000000..0153b32fd --- /dev/null +++ b/src/Randomizer.Multiplayer.Server/Migrations/20230621133443_DeathLink.Designer.cs @@ -0,0 +1,350 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Randomizer.Multiplayer.Server; + +#nullable disable + +namespace Randomizer.Multiplayer.Server.Migrations +{ + [DbContext(typeof(MultiplayerDbContext))] + [Migration("20230621133443_DeathLink")] + partial class DeathLink + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "6.0.11"); + + modelBuilder.Entity("Randomizer.Shared.Multiplayer.MultiplayerBossState", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Boss") + .HasColumnType("INTEGER"); + + b.Property("GameId") + .HasColumnType("INTEGER"); + + b.Property("PlayerId") + .HasColumnType("INTEGER"); + + b.Property("Tracked") + .HasColumnType("INTEGER"); + + b.Property("TrackedTime") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("GameId"); + + b.HasIndex("PlayerId"); + + b.ToTable("MultiplayerBossStates"); + }); + + modelBuilder.Entity("Randomizer.Shared.Multiplayer.MultiplayerDungeonState", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Dungeon") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("GameId") + .HasColumnType("INTEGER"); + + b.Property("PlayerId") + .HasColumnType("INTEGER"); + + b.Property("Tracked") + .HasColumnType("INTEGER"); + + b.Property("TrackedTime") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("GameId"); + + b.HasIndex("PlayerId"); + + b.ToTable("MultiplayerDungeonStates"); + }); + + modelBuilder.Entity("Randomizer.Shared.Multiplayer.MultiplayerGameState", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CreatedDate") + .HasColumnType("TEXT"); + + b.Property("DeathLink") + .HasColumnType("INTEGER"); + + b.Property("Guid") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("LastMessage") + .HasColumnType("TEXT"); + + b.Property("SaveToDatabase") + .HasColumnType("INTEGER"); + + b.Property("Seed") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("SendItemsOnComplete") + .HasColumnType("INTEGER"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.Property("Url") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("ValidationHash") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Version") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("MultiplayerGameStates"); + }); + + modelBuilder.Entity("Randomizer.Shared.Multiplayer.MultiplayerItemState", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("GameId") + .HasColumnType("INTEGER"); + + b.Property("Item") + .HasColumnType("INTEGER"); + + b.Property("PlayerId") + .HasColumnType("INTEGER"); + + b.Property("TrackedTime") + .HasColumnType("TEXT"); + + b.Property("TrackingValue") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("GameId"); + + b.HasIndex("PlayerId"); + + b.ToTable("MultiplayerItemStates"); + }); + + modelBuilder.Entity("Randomizer.Shared.Multiplayer.MultiplayerLocationState", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("GameId") + .HasColumnType("INTEGER"); + + b.Property("LocationId") + .HasColumnType("INTEGER"); + + b.Property("PlayerId") + .HasColumnType("INTEGER"); + + b.Property("Tracked") + .HasColumnType("INTEGER"); + + b.Property("TrackedTime") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("GameId"); + + b.HasIndex("PlayerId"); + + b.ToTable("MultiplayerLocationStates"); + }); + + modelBuilder.Entity("Randomizer.Shared.Multiplayer.MultiplayerPlayerState", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AdditionalData") + .HasColumnType("TEXT"); + + b.Property("Config") + .HasColumnType("TEXT"); + + b.Property("GameId") + .HasColumnType("INTEGER"); + + b.Property("GenerationData") + .HasColumnType("TEXT"); + + b.Property("Guid") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("IsAdmin") + .HasColumnType("INTEGER"); + + b.Property("Key") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("PhoneticName") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("PlayerName") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("WorldId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("GameId"); + + b.ToTable("MultiplayerPlayerStates"); + }); + + modelBuilder.Entity("Randomizer.Shared.Multiplayer.MultiplayerBossState", b => + { + b.HasOne("Randomizer.Shared.Multiplayer.MultiplayerGameState", "Game") + .WithMany() + .HasForeignKey("GameId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Randomizer.Shared.Multiplayer.MultiplayerPlayerState", "Player") + .WithMany("Bosses") + .HasForeignKey("PlayerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Game"); + + b.Navigation("Player"); + }); + + modelBuilder.Entity("Randomizer.Shared.Multiplayer.MultiplayerDungeonState", b => + { + b.HasOne("Randomizer.Shared.Multiplayer.MultiplayerGameState", "Game") + .WithMany() + .HasForeignKey("GameId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Randomizer.Shared.Multiplayer.MultiplayerPlayerState", "Player") + .WithMany("Dungeons") + .HasForeignKey("PlayerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Game"); + + b.Navigation("Player"); + }); + + modelBuilder.Entity("Randomizer.Shared.Multiplayer.MultiplayerItemState", b => + { + b.HasOne("Randomizer.Shared.Multiplayer.MultiplayerGameState", "Game") + .WithMany() + .HasForeignKey("GameId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Randomizer.Shared.Multiplayer.MultiplayerPlayerState", "Player") + .WithMany("Items") + .HasForeignKey("PlayerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Game"); + + b.Navigation("Player"); + }); + + modelBuilder.Entity("Randomizer.Shared.Multiplayer.MultiplayerLocationState", b => + { + b.HasOne("Randomizer.Shared.Multiplayer.MultiplayerGameState", "Game") + .WithMany() + .HasForeignKey("GameId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Randomizer.Shared.Multiplayer.MultiplayerPlayerState", "Player") + .WithMany("Locations") + .HasForeignKey("PlayerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Game"); + + b.Navigation("Player"); + }); + + modelBuilder.Entity("Randomizer.Shared.Multiplayer.MultiplayerPlayerState", b => + { + b.HasOne("Randomizer.Shared.Multiplayer.MultiplayerGameState", "Game") + .WithMany("Players") + .HasForeignKey("GameId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Game"); + }); + + modelBuilder.Entity("Randomizer.Shared.Multiplayer.MultiplayerGameState", b => + { + b.Navigation("Players"); + }); + + modelBuilder.Entity("Randomizer.Shared.Multiplayer.MultiplayerPlayerState", b => + { + b.Navigation("Bosses"); + + b.Navigation("Dungeons"); + + b.Navigation("Items"); + + b.Navigation("Locations"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Randomizer.Multiplayer.Server/Migrations/20230621133443_DeathLink.cs b/src/Randomizer.Multiplayer.Server/Migrations/20230621133443_DeathLink.cs new file mode 100644 index 000000000..196698104 --- /dev/null +++ b/src/Randomizer.Multiplayer.Server/Migrations/20230621133443_DeathLink.cs @@ -0,0 +1,26 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Randomizer.Multiplayer.Server.Migrations +{ + public partial class DeathLink : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "DeathLink", + table: "MultiplayerGameStates", + type: "INTEGER", + nullable: false, + defaultValue: false); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "DeathLink", + table: "MultiplayerGameStates"); + } + } +} diff --git a/src/Randomizer.Multiplayer.Server/Migrations/MultiplayerDbContextModelSnapshot.cs b/src/Randomizer.Multiplayer.Server/Migrations/MultiplayerDbContextModelSnapshot.cs index fef6ee340..d201a4587 100644 --- a/src/Randomizer.Multiplayer.Server/Migrations/MultiplayerDbContextModelSnapshot.cs +++ b/src/Randomizer.Multiplayer.Server/Migrations/MultiplayerDbContextModelSnapshot.cs @@ -87,6 +87,9 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("CreatedDate") .HasColumnType("TEXT"); + b.Property("DeathLink") + .HasColumnType("INTEGER"); + b.Property("Guid") .IsRequired() .HasColumnType("TEXT"); diff --git a/src/Randomizer.Multiplayer.Server/MultiplayerDbService.cs b/src/Randomizer.Multiplayer.Server/MultiplayerDbService.cs index 87797da6c..b0cccd844 100644 --- a/src/Randomizer.Multiplayer.Server/MultiplayerDbService.cs +++ b/src/Randomizer.Multiplayer.Server/MultiplayerDbService.cs @@ -27,9 +27,16 @@ public MultiplayerDbService(IDbContextFactory contextFacto public async Task AddGameToDatabase(MultiplayerGameState state) { if (!_isDatabaseEnabled || !state.SaveToDatabase) return; - await using var dbContext = await _contextFactory.CreateDbContextAsync(); - dbContext.MultiplayerGameStates.Add(state); - await SaveChanges(dbContext, "Unable add game to the database"); + try + { + await using var dbContext = await _contextFactory.CreateDbContextAsync(); + dbContext.MultiplayerGameStates.Add(state); + await SaveChanges(dbContext, $"Unable add game {state.Guid} to the database"); + } + catch (Exception e) + { + _logger.LogError(e, "Unable to add game {Game} to the database", state.Guid); + } } /// @@ -78,7 +85,7 @@ public async Task AddGameToDatabase(MultiplayerGameState state) } catch (Exception ex) { - _logger.LogCritical(ex, "Unable to load game from database"); + _logger.LogCritical(ex, "Unable to load game {Game} from database", guid); return null; } } @@ -91,9 +98,16 @@ public async Task AddGameToDatabase(MultiplayerGameState state) public async Task AddPlayerToGame(MultiplayerGameState gameState, MultiplayerPlayerState playerState) { if (!_isDatabaseEnabled || !gameState.SaveToDatabase) return; - await using var dbContext = await _contextFactory.CreateDbContextAsync(); - dbContext.MultiplayerPlayerStates.Add(playerState); - await SaveChanges(dbContext, $"Unable add player {playerState.Guid} to the database"); + try + { + await using var dbContext = await _contextFactory.CreateDbContextAsync(); + dbContext.MultiplayerPlayerStates.Add(playerState); + await SaveChanges(dbContext, $"Unable to add player {playerState.Guid} to the database"); + } + catch (Exception e) + { + _logger.LogError(e, "Unable to add player {Player} to database", playerState.Guid); + } } /// @@ -103,12 +117,19 @@ public async Task AddPlayerToGame(MultiplayerGameState gameState, MultiplayerPla public async Task SaveGameState(MultiplayerGameState gameState) { if (!_isDatabaseEnabled || !gameState.SaveToDatabase) return; - await using var dbContext = await _contextFactory.CreateDbContextAsync(); - var dbState = dbContext.MultiplayerGameStates - .FirstOrDefault(x => x.Id == gameState.Id); - if (dbState == null) return; - dbState.Copy(gameState); - await SaveChanges(dbContext, $"Unable to save game state {gameState.Guid}"); + try + { + await using var dbContext = await _contextFactory.CreateDbContextAsync(); + var dbState = dbContext.MultiplayerGameStates + .FirstOrDefault(x => x.Id == gameState.Id); + if (dbState == null) return; + dbState.Copy(gameState); + await SaveChanges(dbContext, $"Unable to save game state {gameState.Guid}"); + } + catch (Exception e) + { + _logger.LogError(e, "Unable to save game state {Game}", gameState.Guid); + } } /// @@ -118,15 +139,22 @@ public async Task SaveGameState(MultiplayerGameState gameState) public async Task SaveGameStates(IEnumerable gameStates) { if (!_isDatabaseEnabled) return; - await using var dbContext = await _contextFactory.CreateDbContextAsync(); - var gameIds = gameStates.Where(x => x.SaveToDatabase).Select(x => x.Id).ToList(); - var dbStates = dbContext.MultiplayerGameStates - .Where(x => gameIds.Contains(x.Id)); - foreach (var dbState in dbStates) + try { - dbState.Copy(gameStates.First(x => x.Id == dbState.Id)); + await using var dbContext = await _contextFactory.CreateDbContextAsync(); + var gameIds = gameStates.Where(x => x.SaveToDatabase).Select(x => x.Id).ToList(); + var dbStates = dbContext.MultiplayerGameStates + .Where(x => gameIds.Contains(x.Id)); + foreach (var dbState in dbStates) + { + dbState.Copy(gameStates.First(x => x.Id == dbState.Id)); + } + await SaveChanges(dbContext, "Unable to save game states"); + } + catch (Exception e) + { + _logger.LogError(e, "Unable to save game states"); } - await SaveChanges(dbContext, "Unable to save game states"); } /// @@ -137,11 +165,18 @@ public async Task SaveGameStates(IEnumerable gameStates) public async Task SavePlayerState(MultiplayerGameState gameState, MultiplayerPlayerState playerState) { if (!_isDatabaseEnabled || !gameState.SaveToDatabase) return; - await using var dbContext = await _contextFactory.CreateDbContextAsync(); - var dbState = await dbContext.MultiplayerPlayerStates.FindAsync(playerState.Id); - if (dbState == null) return; - dbState.Copy(playerState); - await SaveChanges(dbContext, $"Unable to save player state for player {playerState.Guid}"); + try + { + await using var dbContext = await _contextFactory.CreateDbContextAsync(); + var dbState = await dbContext.MultiplayerPlayerStates.FindAsync(playerState.Id); + if (dbState == null) return; + dbState.Copy(playerState); + await SaveChanges(dbContext, $"Unable to save player state for player {playerState.Guid}"); + } + catch (Exception e) + { + _logger.LogError(e, "Unable to save player state {Player}", playerState.Guid); + } } /// @@ -152,15 +187,22 @@ public async Task SavePlayerState(MultiplayerGameState gameState, MultiplayerPla public async Task SavePlayerStates(MultiplayerGameState gameState, IEnumerable playerStates) { if (!_isDatabaseEnabled || !gameState.SaveToDatabase) return; - await using var dbContext = await _contextFactory.CreateDbContextAsync(); - var playerIds = playerStates.Select(x => x.Id).ToList(); - var dbStates = dbContext.MultiplayerPlayerStates - .Where(x => playerIds.Contains(x.Id)); - foreach (var dbState in dbStates) + try { - dbState.Copy(playerStates.First(x => x.Id == dbState.Id)); + await using var dbContext = await _contextFactory.CreateDbContextAsync(); + var playerIds = playerStates.Select(x => x.Id).ToList(); + var dbStates = dbContext.MultiplayerPlayerStates + .Where(x => playerIds.Contains(x.Id)); + foreach (var dbState in dbStates) + { + dbState.Copy(playerStates.First(x => x.Id == dbState.Id)); + } + await SaveChanges(dbContext, $"Unable to save player states for game {gameState.Guid}"); + } + catch (Exception e) + { + _logger.LogError(e, "Unable to save player states for game {Game}", gameState.Guid); } - await SaveChanges(dbContext, "Unable to save player states"); } /// @@ -171,12 +213,19 @@ public async Task SavePlayerStates(MultiplayerGameState gameState, IEnumerable @@ -187,12 +236,20 @@ public async Task SaveLocationState(MultiplayerGameState gameState, MultiplayerL public async Task SaveItemState(MultiplayerGameState gameState, MultiplayerItemState itemState) { if (!_isDatabaseEnabled || !gameState.SaveToDatabase) return; - await using var dbContext = await _contextFactory.CreateDbContextAsync(); - var dbState = await dbContext.MultiplayerItemStates.FindAsync(itemState.Id); - if (dbState == null) return; - dbState.TrackingValue = itemState.TrackingValue; - dbState.TrackedTime = itemState.TrackedTime; - await SaveChanges(dbContext, "Unable to save item state"); + try + { + await using var dbContext = await _contextFactory.CreateDbContextAsync(); + var dbState = await dbContext.MultiplayerItemStates.FindAsync(itemState.Id); + if (dbState == null) return; + dbState.TrackingValue = itemState.TrackingValue; + dbState.TrackedTime = itemState.TrackedTime; + await SaveChanges(dbContext, $"Unable to save item state for player {itemState.Player.Guid}"); + } + catch (Exception e) + { + _logger.LogError(e, "Unable to save item state for player {Player}", itemState.Player.Guid); + } + } /// @@ -203,12 +260,20 @@ public async Task SaveItemState(MultiplayerGameState gameState, MultiplayerItemS public async Task SaveDungeonState(MultiplayerGameState gameState, MultiplayerDungeonState dungeonState) { if (!_isDatabaseEnabled || !gameState.SaveToDatabase) return; - await using var dbContext = await _contextFactory.CreateDbContextAsync(); - var dbState = await dbContext.MultiplayerDungeonStates.FindAsync(dungeonState.Id); - if (dbState == null) return; - dbState.Tracked = dungeonState.Tracked; - dbState.TrackedTime = dungeonState.TrackedTime; - await SaveChanges(dbContext, "Unable to save dungeon state"); + try + { + await using var dbContext = await _contextFactory.CreateDbContextAsync(); + var dbState = await dbContext.MultiplayerDungeonStates.FindAsync(dungeonState.Id); + if (dbState == null) return; + dbState.Tracked = dungeonState.Tracked; + dbState.TrackedTime = dungeonState.TrackedTime; + await SaveChanges(dbContext, $"Unable to save dungeon state for player {dungeonState.Player.Guid}"); + } + catch (Exception e) + { + _logger.LogError(e, "Unable to save dungeon state for player {Player}", dungeonState.Player.Guid); + } + } /// @@ -219,12 +284,19 @@ public async Task SaveDungeonState(MultiplayerGameState gameState, MultiplayerDu public async Task SaveBossState(MultiplayerGameState gameState, MultiplayerBossState bossState) { if (!_isDatabaseEnabled || !gameState.SaveToDatabase) return; - await using var dbContext = await _contextFactory.CreateDbContextAsync(); - var dbState = await dbContext.MultiplayerBossStates.FindAsync(bossState.Id); - if (dbState == null) return; - dbState.Tracked = bossState.Tracked; - dbState.TrackedTime = bossState.TrackedTime; - await SaveChanges(dbContext, $"Unable to save boss state"); + try + { + await using var dbContext = await _contextFactory.CreateDbContextAsync(); + var dbState = await dbContext.MultiplayerBossStates.FindAsync(bossState.Id); + if (dbState == null) return; + dbState.Tracked = bossState.Tracked; + dbState.TrackedTime = bossState.TrackedTime; + await SaveChanges(dbContext, $"Unable to save boss state for player {bossState.Player.Guid}"); + } + catch (Exception e) + { + _logger.LogError(e, "Unable to save boss state for player {Player}", bossState.Player.Guid); + } } /// @@ -236,85 +308,93 @@ public async Task SaveBossState(MultiplayerGameState gameState, MultiplayerBossS public async Task SavePlayerWorld(MultiplayerGameState gameState, MultiplayerPlayerState playerState, PlayerWorldUpdates updates) { if (!_isDatabaseEnabled || !gameState.SaveToDatabase || !updates.HasUpdates) return; - await using var dbContext = await _contextFactory.CreateDbContextAsync(); - - // Update locations - var updateIds = updates.Locations.Select(x => x.Id).ToList(); - var dbLocations = dbContext.MultiplayerLocationStates - .Where(x => x.PlayerId == playerState.Id && updateIds.Contains(x.Id)) - .ToDictionary(x => x.Id, x => x); - foreach (var updateData in updates.Locations) - { - if (dbLocations.ContainsKey(updateData.Id)) - { - var dbData = dbLocations[updateData.Id]; - dbData.Tracked = updateData.Tracked; - dbData.TrackedTime = updateData.TrackedTime; - } - else - { - dbContext.MultiplayerLocationStates.Add(updateData); - } - } - // Update items - updateIds = updates.Items.Select(x => x.Id).ToList(); - var dbItems = dbContext.MultiplayerItemStates - .Where(x => x.PlayerId == playerState.Id && updateIds.Contains(x.Id)) - .ToDictionary(x => x.Id, x => x); - foreach (var updateData in updates.Items) + try { - if (dbItems.ContainsKey(updateData.Id)) + await using var dbContext = await _contextFactory.CreateDbContextAsync(); + + // Update locations + var updateIds = updates.Locations.Select(x => x.Id).ToList(); + var dbLocations = dbContext.MultiplayerLocationStates + .Where(x => x.PlayerId == playerState.Id && updateIds.Contains(x.Id)) + .ToDictionary(x => x.Id, x => x); + foreach (var updateData in updates.Locations) { - var dbData = dbItems[updateData.Id]; - dbData.TrackingValue = updateData.TrackingValue; - dbData.TrackedTime = updateData.TrackedTime; + if (dbLocations.ContainsKey(updateData.Id)) + { + var dbData = dbLocations[updateData.Id]; + dbData.Tracked = updateData.Tracked; + dbData.TrackedTime = updateData.TrackedTime; + } + else + { + dbContext.MultiplayerLocationStates.Add(updateData); + } } - else + + // Update items + updateIds = updates.Items.Select(x => x.Id).ToList(); + var dbItems = dbContext.MultiplayerItemStates + .Where(x => x.PlayerId == playerState.Id && updateIds.Contains(x.Id)) + .ToDictionary(x => x.Id, x => x); + foreach (var updateData in updates.Items) { - dbContext.MultiplayerItemStates.Add(updateData); + if (dbItems.ContainsKey(updateData.Id)) + { + var dbData = dbItems[updateData.Id]; + dbData.TrackingValue = updateData.TrackingValue; + dbData.TrackedTime = updateData.TrackedTime; + } + else + { + dbContext.MultiplayerItemStates.Add(updateData); + } } - } - // Update dungeons - updateIds = updates.Dungeons.Select(x => x.Id).ToList(); - var dbDungeons = dbContext.MultiplayerDungeonStates - .Where(x => x.PlayerId == playerState.Id && updateIds.Contains(x.Id)) - .ToDictionary(x => x.Id, x => x); - foreach (var updateData in updates.Dungeons) - { - if (dbDungeons.ContainsKey(updateData.Id)) + // Update dungeons + updateIds = updates.Dungeons.Select(x => x.Id).ToList(); + var dbDungeons = dbContext.MultiplayerDungeonStates + .Where(x => x.PlayerId == playerState.Id && updateIds.Contains(x.Id)) + .ToDictionary(x => x.Id, x => x); + foreach (var updateData in updates.Dungeons) { - var dbData = dbDungeons[updateData.Id]; - dbData.Tracked = updateData.Tracked; - dbData.TrackedTime = updateData.TrackedTime; + if (dbDungeons.ContainsKey(updateData.Id)) + { + var dbData = dbDungeons[updateData.Id]; + dbData.Tracked = updateData.Tracked; + dbData.TrackedTime = updateData.TrackedTime; + } + else + { + dbContext.MultiplayerDungeonStates.Add(updateData); + } } - else + + // Update bosses + updateIds = updates.Bosses.Select(x => x.Id).ToList(); + var dbBosses = dbContext.MultiplayerBossStates + .Where(x => x.PlayerId == playerState.Id && updateIds.Contains(x.Id)) + .ToDictionary(x => x.Id, x => x); + foreach (var updateData in updates.Bosses) { - dbContext.MultiplayerDungeonStates.Add(updateData); + if (dbBosses.ContainsKey(updateData.Id)) + { + var dbData = dbBosses[updateData.Id]; + dbData.Tracked = updateData.Tracked; + dbData.TrackedTime = updateData.TrackedTime; + } + else + { + dbContext.MultiplayerBossStates.Add(updateData); + } } - } - // Update bosses - updateIds = updates.Bosses.Select(x => x.Id).ToList(); - var dbBosses = dbContext.MultiplayerBossStates - .Where(x => x.PlayerId == playerState.Id && updateIds.Contains(x.Id)) - .ToDictionary(x => x.Id, x => x); - foreach (var updateData in updates.Bosses) + await SaveChanges(dbContext, $"Unable to save player world {playerState.Guid}"); + } + catch (Exception e) { - if (dbBosses.ContainsKey(updateData.Id)) - { - var dbData = dbBosses[updateData.Id]; - dbData.Tracked = updateData.Tracked; - dbData.TrackedTime = updateData.TrackedTime; - } - else - { - dbContext.MultiplayerBossStates.Add(updateData); - } + _logger.LogError(e, "Unable to save player world {Player}", playerState.Guid); } - - await SaveChanges(dbContext, $"Unable to save player world {playerState.Guid}"); } /// @@ -325,11 +405,18 @@ public async Task SavePlayerWorld(MultiplayerGameState gameState, MultiplayerPla public async Task DeletePlayerState(MultiplayerGameState gameState, MultiplayerPlayerState playerState) { if (!_isDatabaseEnabled || !gameState.SaveToDatabase) return; - await using var dbContext = await _contextFactory.CreateDbContextAsync(); - var dbState = await dbContext.MultiplayerPlayerStates.FindAsync(playerState.Id); - if (dbState == null) return; - dbContext.MultiplayerPlayerStates.Remove(dbState); - await SaveChanges(dbContext, $"Unable to delete player state {playerState.Guid}"); + try + { + await using var dbContext = await _contextFactory.CreateDbContextAsync(); + var dbState = await dbContext.MultiplayerPlayerStates.FindAsync(playerState.Id); + if (dbState == null) return; + dbContext.MultiplayerPlayerStates.Remove(dbState); + await SaveChanges(dbContext, $"Unable to delete player state {playerState.Guid}"); + } + catch (Exception e) + { + _logger.LogError(e, "Unable to delete player state {Player}", playerState.Guid); + } } /// @@ -340,18 +427,26 @@ public async Task DeletePlayerState(MultiplayerGameState gameState, MultiplayerP public async Task> DeleteOldGameStates(int expirationDays) { if (!_isDatabaseEnabled) return new List(); - await using var dbContext = await _contextFactory.CreateDbContextAsync(); - var expirationDate = DateTimeOffset.Now - TimeSpan.FromDays(expirationDays); - var dbStates = dbContext.MultiplayerGameStates.Where(x => x.LastMessage.CompareTo(expirationDate) < 0); - var deletedGuids = new List(); - foreach (var dbState in dbStates) + try { - dbContext.Remove(dbState); - deletedGuids.Add(dbState.Guid); - } + await using var dbContext = await _contextFactory.CreateDbContextAsync(); + var expirationDate = DateTimeOffset.Now - TimeSpan.FromDays(expirationDays); + var dbStates = dbContext.MultiplayerGameStates.Where(x => x.LastMessage.CompareTo(expirationDate) < 0); + var deletedGuids = new List(); + foreach (var dbState in dbStates) + { + dbContext.Remove(dbState); + deletedGuids.Add(dbState.Guid); + } - await SaveChanges(dbContext, "Unable to delete old game saves"); - return deletedGuids; + await SaveChanges(dbContext, "Unable to delete old game saves"); + return deletedGuids; + } + catch (Exception e) + { + _logger.LogError(e, "Unable to delete old game saves"); + return new List(); + } } private async Task SaveChanges(MultiplayerDbContext dbContext, string errorMessage) diff --git a/src/Randomizer.Multiplayer.Server/Randomizer.Multiplayer.Server.csproj b/src/Randomizer.Multiplayer.Server/Randomizer.Multiplayer.Server.csproj index 4c0f31254..f2593917a 100644 --- a/src/Randomizer.Multiplayer.Server/Randomizer.Multiplayer.Server.csproj +++ b/src/Randomizer.Multiplayer.Server/Randomizer.Multiplayer.Server.csproj @@ -4,7 +4,7 @@ net6.0 enable enable - 2.0.0 + 2.1.0 False