public async Task <ActionResult <LootListDto> > PostLootList(long characterId, byte phase, [FromBody] LootListSubmissionDto dto, [FromServices] IdGen.IIdGenerator <long> idGenerator) { var authorizationResult = await _authorizationService.AuthorizeAsync(User, characterId, AppPolicies.CharacterOwnerOrAdmin); if (!authorizationResult.Succeeded) { return(Unauthorized()); } var bracketTemplates = await _context.Brackets.AsNoTracking().Where(b => b.Phase == phase).OrderBy(b => b.Index).ToListAsync(); if (bracketTemplates.Count == 0) { return(NotFound()); } if (!ModelState.IsValid) { return(ValidationProblem()); } var character = await _context.Characters.FindAsync(characterId); if (character is null) { return(NotFound()); } if (character.Deactivated) { return(Problem("Character has been deactivated.")); } if (await _context.CharacterLootLists.AsNoTracking().AnyAsync(ll => ll.CharacterId == character.Id && ll.Phase == phase)) { return(Problem("A loot list for that character and phase already exists.")); } if (!dto.MainSpec.IsClass(character.Class)) { ModelState.AddModelError(nameof(dto.MainSpec), "Selected specialization does not fit the player's class."); return(ValidationProblem()); } if (dto.OffSpec != default && !dto.OffSpec.IsClass(character.Class)) { ModelState.AddModelError(nameof(dto.OffSpec), "Selected specialization does not fit the player's class."); return(ValidationProblem()); } var list = new CharacterLootList { CharacterId = character.Id, Character = character, Entries = new List <LootListEntry>(28), Status = LootListStatus.Editing, MainSpec = dto.MainSpec, OffSpec = dto.OffSpec, Phase = phase }; var entries = new List <LootListEntry>(); var brackets = await _context.Brackets .AsNoTracking() .Where(b => b.Phase == phase) .OrderBy(b => b.Index) .ToListAsync(); foreach (var bracket in brackets) { for (byte rank = bracket.MinRank; rank <= bracket.MaxRank; rank++) { for (int col = 0; col < bracket.MaxItems; col++) { var entry = new LootListEntry(idGenerator.CreateId()) { LootList = list, Rank = rank }; _context.LootListEntries.Add(entry); entries.Add(entry); } } } _context.CharacterLootLists.Add(list); await _context.SaveChangesAsync(); _telemetry.TrackEvent("LootListCreated", User, props => { props["CharacterId"] = character.Id.ToString(); props["CharacterName"] = character.Name; props["Phase"] = list.Phase.ToString(); }); var scope = await _context.GetCurrentPriorityScopeAsync(); var observedDates = await _context.Raids .AsNoTracking() .Where(r => r.RaidTeamId == character.TeamId) .OrderByDescending(r => r.StartedAt) .Select(r => r.StartedAt.Date) .Distinct() .Take(scope.ObservedAttendances) .ToListAsync(); var attendance = await _context.RaidAttendees .AsNoTracking() .Where(x => !x.IgnoreAttendance && x.RemovalId == null && x.CharacterId == character.Id && x.Raid.RaidTeamId == character.TeamId) .Select(x => x.Raid.StartedAt.Date) .Where(date => observedDates.Contains(date)) .Distinct() .CountAsync(); var now = _serverTimeZoneInfo.TimeZoneNow(); var donationMatrix = await _context.GetDonationMatrixAsync(d => d.CharacterId == characterId, scope); var donations = donationMatrix.GetCreditForMonth(characterId, now); var returnDto = new LootListDto { ApprovedBy = null, Bonuses = PrioCalculator.GetListBonuses(scope, attendance, character.MemberStatus, donations).ToList(), CharacterId = character.Id, CharacterMemberStatus = character.MemberStatus, CharacterName = character.Name, MainSpec = list.MainSpec, OffSpec = list.OffSpec, Phase = list.Phase, Status = list.Status, TeamId = character.TeamId, Timestamp = list.Timestamp }; if (returnDto.TeamId.HasValue) { returnDto.TeamName = await _context.RaidTeams.AsNoTracking().Where(t => t.Id == returnDto.TeamId).Select(t => t.Name).FirstOrDefaultAsync(); } foreach (var entry in entries) { var bracket = brackets.Find(b => entry.Rank >= b.MinRank && entry.Rank <= b.MaxRank); Debug.Assert(bracket is not null); returnDto.Entries.Add(new LootListEntryDto { Bracket = bracket.Index, BracketAllowsOffspec = bracket.AllowOffspec, BracketAllowsTypeDuplicates = bracket.AllowTypeDuplicates, Id = entry.Id, Rank = entry.Rank }); } return(CreatedAtAction(nameof(Get), new { characterId = character.Id, phase }, returnDto)); }
public async Task <ActionResult <EncounterDropDto> > PutAssign(long id, [FromBody] AwardDropSubmissionDto dto, [FromServices] DiscordClientProvider dcp) { var now = _realmTimeZone.TimeZoneNow(); var drop = await _context.Drops.FindAsync(id); if (drop is null) { return(NotFound()); } var raid = await _context.Raids .AsNoTracking() .Where(r => r.Id == drop.EncounterKillRaidId) .Select(r => new { r.RaidTeamId, r.LocksAt }) .FirstOrDefaultAsync(); if (raid is null) { return(NotFound()); } var authResult = await _authorizationService.AuthorizeAsync(User, raid.RaidTeamId, AppPolicies.LootMaster); if (!authResult.Succeeded) { return(Unauthorized()); } if (DateTimeOffset.UtcNow > raid.LocksAt) { return(Problem("Can't alter a locked raid.")); } if (dto.WinnerId.HasValue && drop.WinnerId.HasValue) { ModelState.AddModelError(nameof(dto.WinnerId), "Existing winner must be cleared before setting a new winner."); return(ValidationProblem()); } drop.AwardedAt = now; drop.AwardedBy = User.GetDiscordId(); var scope = await _context.GetCurrentPriorityScopeAsync(); var teamId = raid.RaidTeamId; var attendances = await _context.GetAttendanceTableAsync(teamId, scope.ObservedAttendances); var donationMatrix = await _context.GetDonationMatrixAsync(d => d.Character.Attendances.Any(a => a.RaidId == drop.EncounterKillRaidId), scope); var presentTeamRaiders = await _context.CharacterEncounterKills .AsTracking() .Where(cek => cek.EncounterKillEncounterId == drop.EncounterKillEncounterId && cek.EncounterKillRaidId == drop.EncounterKillRaidId && cek.EncounterKillTrashIndex == drop.EncounterKillTrashIndex) .Select(cek => cek.Character) .Select(ConvertToDropInfo(drop.ItemId)) .ToListAsync(); await _context.Entry(drop).Collection(drop => drop.Passes).LoadAsync(); drop.Passes.Clear(); if (dto.WinnerId.HasValue) { var winner = presentTeamRaiders.Find(e => e.Id == dto.WinnerId); if (winner is null) { ModelState.AddModelError(nameof(dto.WinnerId), "Character was not present for the kill."); return(ValidationProblem()); } int?winnerPrio = null; if (winner.Entry is not null) { winnerPrio = winner.Entry.Rank; var passes = await _context.DropPasses .AsTracking() .Where(p => p.LootListEntryId == winner.Entry.Id && p.RemovalId == null) .ToListAsync(); Debug.Assert(passes.Count == winner.Entry.Passes); var donated = donationMatrix.GetCreditForMonth(winner.Id, now); attendances.TryGetValue(winner.Id, out int attended); foreach (var bonus in PrioCalculator.GetAllBonuses(scope, attended, winner.MemberStatus, donated, passes.Count, winner.Enchanted, winner.Prepared)) { winnerPrio = winnerPrio.Value + bonus.Value; } drop.WinningEntry = await _context.LootListEntries.FindAsync(winner.Entry.Id); Debug.Assert(drop.WinningEntry is not null); drop.WinningEntry.Drop = drop; drop.WinningEntry.DropId = drop.Id; foreach (var pass in passes) { pass.WonEntryId = winner.Entry.Id; } } drop.WinnerId = winner.Id; foreach (var killer in presentTeamRaiders) { if (killer.Entry is not null && killer.TeamId == teamId && killer != winner) { var thisPrio = (int)killer.Entry.Rank; var donated = donationMatrix.GetCreditForMonth(killer.Id, now); attendances.TryGetValue(killer.Id, out int attended); foreach (var bonus in PrioCalculator.GetAllBonuses(scope, attended, killer.MemberStatus, donated, killer.Entry.Passes, killer.Enchanted, killer.Prepared)) { thisPrio += bonus.Value; } _context.DropPasses.Add(new DropPass { Drop = drop, DropId = drop.Id, CharacterId = killer.Id, RelativePriority = thisPrio - (winnerPrio ?? 0), LootListEntryId = killer.Entry.Id }); } } } else { var oldWinningEntry = await _context.LootListEntries .AsTracking() .Where(e => e.DropId == drop.Id) .SingleOrDefaultAsync(); drop.Winner = null; drop.WinnerId = null; drop.WinningEntry = null; if (oldWinningEntry is not null) { oldWinningEntry.Drop = null; oldWinningEntry.DropId = null; await foreach (var pass in _context.DropPasses .AsTracking() .Where(pass => pass.WonEntryId == oldWinningEntry.Id) .AsAsyncEnumerable()) { pass.WonEntryId = null; } } } await _context.SaveChangesAsync(); var kill = await _context.EncounterKills .AsNoTracking() .Where(kill => kill.EncounterId == drop.EncounterKillEncounterId && kill.RaidId == drop.EncounterKillRaidId && kill.TrashIndex == drop.EncounterKillTrashIndex) .Select(kill => new { kill.DiscordMessageId, kill.KilledAt, kill.RaidId, TeamName = kill.Raid.RaidTeam.Name, EncounterName = kill.Encounter.Name }) .FirstAsync(); var drops = new List <(uint, string, string?)>(); await foreach (var d in _context.Drops .AsNoTracking() .Where(d => d.EncounterKillEncounterId == drop.EncounterKillEncounterId && d.EncounterKillRaidId == drop.EncounterKillRaidId && d.EncounterKillTrashIndex == drop.EncounterKillTrashIndex) .Select(d => new { d.ItemId, ItemName = d.Item.Name, WinnerName = (string?)d.Winner !.Name })