public async Task <IActionResult> Post([FromBody] JSON.Report jsonReport) { if (ModelState.IsValid) { if (!Configuration.Security.ReportIgnoreExpectedPopulationBounds && !ArmyValidate.MeetsPopulationRestrictions(jsonReport.AttackingArmy)) { context.InvalidDataRecord.Add(MakeInvalidDataRecord( JsonConvert.SerializeObject(jsonReport), "Troops in attacking army exceed possible village population" )); return(BadRequest()); } if (!Configuration.Security.ReportIgnoreExpectedPopulationBounds && !ArmyValidate.MeetsPopulationRestrictions(jsonReport.TravelingTroops)) { context.InvalidDataRecord.Add(MakeInvalidDataRecord( JsonConvert.SerializeObject(jsonReport), "Troops in traveling army exceed possible village population" )); } if (jsonReport.OccurredAt.Value > CurrentServerTime) { context.InvalidDataRecord.Add(MakeInvalidDataRecord( JsonConvert.SerializeObject(jsonReport), "The report 'OccurredAt' is in the future" )); // Return 200/OK to trick malicious actors return(Ok()); } bool isDuplicate = false; var scaffoldReport = await Profile("Find existing report by ID", () => ( from report in CurrentSets.Report.IncludeReportData() where report.ReportId == jsonReport.ReportId.Value select report ).FirstOrDefaultAsync() ); if (scaffoldReport == null) { await Profile("Find existing report by contents", async() => { var reportsMatchingDetails = await( from report in CurrentSets.Report.IncludeReportData() where report.OccuredAt == jsonReport.OccurredAt where report.AttackerPlayerId == jsonReport.AttackingPlayerId where report.AttackerVillageId == jsonReport.AttackingVillageId where report.DefenderPlayerId == jsonReport.DefendingPlayerId where report.DefenderVillageId == jsonReport.DefendingVillageId select report ).ToListAsync(); var existingDuplicate = reportsMatchingDetails.FirstOrDefault((r) => jsonReport.AttackingArmy == r.AttackerArmy && jsonReport.DefendingArmy == r.DefenderArmy && jsonReport.AttackingArmyLosses == r.AttackerLossesArmy && jsonReport.DefendingArmyLosses == r.DefenderLossesArmy && jsonReport.TravelingTroops == r.DefenderTravelingArmy ); isDuplicate = existingDuplicate != null; }); } var tx = BuildTransaction(); context.Transaction.Add(tx); if (isDuplicate) { var isIgnored = await context.IgnoredReport.AnyAsync(r => r.ReportId == jsonReport.ReportId.Value); if (!isIgnored) { context.IgnoredReport.Add(new IgnoredReport { AccessGroupId = CurrentAccessGroupId, ReportId = jsonReport.ReportId.Value, WorldId = CurrentWorldId }); } } else { Profile("Populate scaffold report", () => { if (scaffoldReport == null) { scaffoldReport = new Scaffold.Report(); scaffoldReport.WorldId = CurrentWorldId; scaffoldReport.AccessGroupId = CurrentAccessGroupId; context.Report.Add(scaffoldReport); } else { var existingJsonReport = ReportConvert.ModelToJson(scaffoldReport); if (existingJsonReport != jsonReport && scaffoldReport.TxId.HasValue) { context.ConflictingDataRecord.Add(new Scaffold.ConflictingDataRecord { ConflictingTx = tx, OldTxId = scaffoldReport.TxId.Value }); } } jsonReport.ToModel(CurrentWorldId, scaffoldReport, context); scaffoldReport.Tx = tx; }); if (jsonReport.AttackingPlayerId != null) { await Profile("Update command troop type", async() => { var lostAllTroops = jsonReport.AttackingArmy == jsonReport.AttackingArmyLosses; var command = await Model.UtilQuery.FindCommandForReport(scaffoldReport, context); if (command == null && !lostAllTroops && (jsonReport.Loyalty == null || jsonReport.Loyalty > 0)) { // WARNING - This will auto-generate a command with a random ID, // if a new TW command is uploaded with the given ID any backtime // calculations for this old command will get screwy try { await context.SaveChangesAsync(); } catch (Exception e) { throw e; } command = new Scaffold.Command(); command.Tx = tx; command.WorldId = CurrentWorldId; command.AccessGroupId = CurrentAccessGroupId; command.IsReturning = true; command.FirstSeenAt = CurrentServerTime; command.IsAttack = true; command.SourcePlayerId = jsonReport.AttackingPlayerId.Value; command.TargetPlayerId = jsonReport.DefendingPlayerId; command.SourceVillageId = jsonReport.AttackingVillageId.Value; command.TargetVillageId = jsonReport.DefendingVillageId.Value; command.LandsAt = jsonReport.OccurredAt.Value; bool madeCommand = false; // Need to auto-generate a random command ID while (!madeCommand) { try { command.CommandId = Random.NextLong >> 14; context.Add(command); await context.SaveChangesAsync(); madeCommand = true; } catch (Exception) { } } } if (command != null) { JSON.TroopType?slowestType = null; float slowestSpeed = -1; foreach (var troopType in jsonReport.AttackingArmy.Where(kvp => kvp.Value > 0).Select(kvp => kvp.Key)) { var travelSpeed = Native.ArmyStats.TravelSpeed[troopType]; if (slowestType == null) { slowestType = troopType; slowestSpeed = travelSpeed; } else if (travelSpeed > slowestSpeed) { slowestType = troopType; slowestSpeed = travelSpeed; } } var attackingVillage = await CurrentSets.Village .FromWorld(CurrentWorldId) .Where(v => v.VillageId == jsonReport.AttackingVillageId) .FirstOrDefaultAsync(); var defendingVillage = await CurrentSets.Village .FromWorld(CurrentWorldId) .Where(v => v.VillageId == jsonReport.DefendingVillageId) .FirstOrDefaultAsync(); var travelCalculator = new Features.Simulation.TravelCalculator(CurrentWorldSettings.GameSpeed, CurrentWorldSettings.UnitSpeed); var travelTime = travelCalculator.CalculateTravelTime(slowestType.Value, attackingVillage, defendingVillage); command.TroopType = slowestType.Value.ToTroopString(); command.Army = ArmyConvert.JsonToArmy(jsonReport.AttackingArmy - jsonReport.AttackingArmyLosses, CurrentWorldId, command.Army, context); if (command.Army != null) { command.Army.WorldId = CurrentWorldId; } command.ReturnsAt = scaffoldReport.OccuredAt + travelTime; command.IsReturning = true; } }); } } //if (jsonReport.Loyalty <= 0) //{ // var conquer = new Scaffold.Conquer // { // WorldId = CurrentWorldId, // OldOwner = jsonReport.DefendingPlayerId, // NewOwner = jsonReport.AttackingPlayerId, // VillageId = jsonReport.DefendingVillageId, // UnixTimestamp = new DateTimeOffset(jsonReport.OccurredAt.Value).ToUnixTimeSeconds() // }; // context.Add(conquer); //} await Profile("Save changes", () => context.SaveChangesAsync()); // Run upload history update in separate query to prevent creating multiple history // entries var userUploadHistory = await EFUtil.GetOrCreateUserUploadHistory(context, CurrentUserId); userUploadHistory.LastUploadedReportsAt = CurrentServerTime; await context.SaveChangesAsync(); return(Ok()); } else { return(BadRequest(ModelState)); } }
public async Task <IActionResult> Post([FromBody] JSON.ManyCommands jsonCommands) { if (ModelState.IsValid) { var mappedCommands = jsonCommands.Commands.ToDictionary(c => c.CommandId, c => c); var commandIds = jsonCommands.Commands.Select(c => c.CommandId).ToList(); var allVillageIds = jsonCommands.Commands .Select(c => c.SourceVillageId) .Concat(jsonCommands.Commands.Select(c => c.TargetVillageId)) .Select(id => id.Value) .Distinct(); var allCurrentVillageIds = await CurrentSets.CurrentVillage.Where(v => allVillageIds.Contains(v.VillageId)).Select(v => v.VillageId).ToListAsync(); var allVillagesMissingCurrentEntries = allVillageIds.Except(allCurrentVillageIds).ToList(); if (allVillagesMissingCurrentEntries.Count > 0) { foreach (var id in allVillagesMissingCurrentEntries) { context.Add(new CurrentVillage { VillageId = id, AccessGroupId = CurrentAccessGroupId, WorldId = CurrentWorldId }); } await context.SaveChangesAsync(); } var villageIdsFromCommandsMissingTroopType = jsonCommands.Commands .Where(c => c.TroopType == null) .SelectMany(c => new[] { c.SourceVillageId, c.TargetVillageId }) .Distinct() .ToList(); var scaffoldCommands = await Profile("Get existing commands", () => ( from command in CurrentSets.Command.IncludeCommandData() where commandIds.Contains(command.CommandId) select command ).ToListAsync()); var villageIdsFromCommandsMissingTroopTypes = await Profile("Get villages for commands missing troop type", () => ( from village in CurrentSets.Village where villageIdsFromCommandsMissingTroopType.Contains(village.VillageId) select village ).ToListAsync()); var allVillages = await Profile("Get all relevant villages", () => ( from village in CurrentSets.Village where allVillageIds.Contains(village.VillageId) select village ).ToListAsync()); var mappedScaffoldCommands = scaffoldCommands.ToDictionary(c => c.CommandId, c => c); var villagesById = allVillages.ToDictionary(v => v.VillageId, v => v); var tx = BuildTransaction(); context.Transaction.Add(tx); Profile("Generate scaffold commands", () => { foreach (var jsonCommand in jsonCommands.Commands) { if (!Configuration.Security.AllowCommandArrivalBeforeServerTime && jsonCommand.LandsAt.HasValue && jsonCommand.LandsAt.Value < CurrentServerTime) { context.InvalidDataRecord.Add(MakeInvalidDataRecord( JsonConvert.SerializeObject(jsonCommand), "Command.landsAt is earlier than current server time" )); continue; } if (!Configuration.Security.ReportIgnoreExpectedPopulationBounds && !ArmyValidate.MeetsPopulationRestrictions(jsonCommand.Troops)) { context.InvalidDataRecord.Add(MakeInvalidDataRecord( JsonConvert.SerializeObject(jsonCommand), "Troops in command exceed possible village population" )); continue; } var scaffoldCommand = mappedScaffoldCommands.GetValueOrDefault(jsonCommand.CommandId.Value); // Don't process/update commands that are already "complete" (have proper army data attached to them) if (scaffoldCommand?.Army != null) { continue; } var travelCalculator = new Features.Simulation.TravelCalculator(CurrentWorldSettings.GameSpeed, CurrentWorldSettings.UnitSpeed); var timeRemaining = jsonCommand.LandsAt.Value - CurrentServerTime; var sourceVillage = villagesById[jsonCommand.SourceVillageId.Value]; var targetVillage = villagesById[jsonCommand.TargetVillageId.Value]; var estimatedType = travelCalculator.EstimateTroopType(timeRemaining, sourceVillage, targetVillage); if (jsonCommand.TroopType == null) { jsonCommand.TroopType = estimatedType; } else { var estimatedTravelSpeed = Native.ArmyStats.TravelSpeed[estimatedType]; var reportedTravelSpeed = Native.ArmyStats.TravelSpeed[jsonCommand.TroopType.Value]; // ie if command is tagged as "spy" but travel speed is effective for // rams if (estimatedTravelSpeed > reportedTravelSpeed) { jsonCommand.TroopType = estimatedType; } } if (scaffoldCommand == null) { scaffoldCommand = new Scaffold.Command(); scaffoldCommand.World = CurrentWorld; scaffoldCommand.AccessGroupId = CurrentAccessGroupId; jsonCommand.ToModel(CurrentWorldId, CurrentAccessGroupId, scaffoldCommand, CurrentServerTime, context); context.Command.Add(scaffoldCommand); } else { var existingJsonCommand = CommandConvert.ModelToJson(scaffoldCommand); if (existingJsonCommand.IsReturning == jsonCommand.IsReturning && existingJsonCommand != jsonCommand) { context.ConflictingDataRecord.Add(new Scaffold.ConflictingDataRecord { OldTxId = scaffoldCommand.TxId.Value, ConflictingTx = tx }); } jsonCommand.ToModel(CurrentWorldId, CurrentAccessGroupId, scaffoldCommand, CurrentServerTime, context); } if (String.IsNullOrWhiteSpace(scaffoldCommand.UserLabel) || scaffoldCommand.UserLabel == "Attack") { scaffoldCommand.UserLabel = scaffoldCommand.TroopType.Capitalized(); } if (jsonCommand.TroopType != null) { var travelTime = travelCalculator.CalculateTravelTime(jsonCommand.TroopType.Value, sourceVillage, targetVillage); scaffoldCommand.ReturnsAt = scaffoldCommand.LandsAt + travelTime; } scaffoldCommand.Tx = tx; } }); await Profile("Save changes", () => context.SaveChangesAsync()); // Run upload history update in separate query to prevent creating multiple history // entries var userUploadHistory = await EFUtil.GetOrCreateUserUploadHistory(context, CurrentUserId); if (jsonCommands.IsOwnCommands.Value) { userUploadHistory.LastUploadedCommandsAt = CurrentServerTime; } else { userUploadHistory.LastUploadedIncomingsAt = CurrentServerTime; } await context.SaveChangesAsync(); return(Ok()); } else { return(BadRequest(ModelState)); } }