public static Scaffold.Command JsonToModel(JSON.Command command, short worldId, int accessGroupId, Scaffold.Command existingCommand, DateTime currentTime, Scaffold.VaultContext context = null) { if (command == null) { if (existingCommand != null && context != null) { context.Remove(existingCommand); } return(null); } Scaffold.Command result; if (existingCommand != null) { result = existingCommand; } else { result = new Scaffold.Command(); result.CommandId = command.CommandId.Value; result.WorldId = worldId; result.AccessGroupId = accessGroupId; if (context != null) { context.Add(result); } } result.CommandId = command.CommandId.Value; result.UserLabel = result.UserLabel ?? command.UserLabel; // Don't overwrite existing labels result.SourcePlayerId = command.SourcePlayerId.Value; result.SourceVillageId = command.SourceVillageId.Value; result.TargetPlayerId = command.TargetPlayerId; result.TargetVillageId = command.TargetVillageId.Value; result.LandsAt = command.LandsAt.Value; result.FirstSeenAt = result.FirstSeenAt == DateTime.MinValue ? currentTime : result.FirstSeenAt; result.IsAttack = command.CommandType == JSON.CommandType.Attack; result.IsReturning = command.IsReturning.Value; result.Army = ArmyConvert.JsonToArmy(command.Troops, worldId, result.Army, context); if (result.TroopType == null) { result.TroopType = TroopTypeConvert.TroopTypeToString(command.TroopType); } else if (command.TroopType != null) { var currentType = result.TroopType.ToTroopType(); var updatedType = command.TroopType.Value; if (Native.ArmyStats.TravelSpeed[currentType] < Native.ArmyStats.TravelSpeed[updatedType]) { result.TroopType = updatedType.ToTroopString(); } } return(result); }
public static JSON.Command ModelToJson(Scaffold.Command command) { var result = new JSON.Command(); result.CommandId = command.CommandId; result.UserLabel = command.UserLabel; result.SourcePlayerId = command.SourcePlayerId; result.SourceVillageId = command.SourceVillageId; result.TargetPlayerId = command.TargetPlayerId; result.TargetVillageId = command.TargetVillageId; result.LandsAt = command.LandsAt; result.CommandType = command.IsAttack ? JSON.CommandType.Attack : JSON.CommandType.Support; result.IsReturning = command.IsReturning; result.TroopType = TroopTypeConvert.StringToTroopType(command.TroopType); result.Troops = ArmyConvert.ArmyToJson(command.Army); return(result); }
public static JSON.Command ToJson(this Scaffold.Command command) => ModelToJson(command);
public static Scaffold.Command ToModel(this JSON.Command command, short worldId, int accessGroupId, Scaffold.Command existingCommand, DateTime currentTime, Scaffold.VaultContext context = null) => JsonToModel(command, worldId, accessGroupId, existingCommand, currentTime, context);
public async Task <IActionResult> GetBacktimePlan() { var serverTime = CurrentServerTime; var invalidTribeIds = await( from tribe in CurrentSets.Ally join player in CurrentSets.Player on tribe.TribeId equals player.TribeId join user in CurrentSets.User on player.PlayerId equals user.PlayerId where user.Enabled select tribe.TribeId ).Distinct().ToListAsync(); var invalidPlayerIds = await( from player in CurrentSets.Player join user in CurrentSets.User on player.PlayerId equals user.PlayerId into user where user.Any(u => u.Enabled) || (player.TribeId != null && invalidTribeIds.Contains(player.TribeId.Value)) select player.PlayerId ).ToListAsync(); var invalidVillageIds = await( from village in CurrentSets.Village where village.PlayerId != null && invalidPlayerIds.Contains(village.PlayerId.Value) select village.VillageId ).ToListAsync(); var ownVillages = await( from village in CurrentSets.Village join currentVillage in CurrentSets.CurrentVillage.Include(cv => cv.ArmyAtHome) on village.VillageId equals currentVillage.VillageId where currentVillage.ArmyAtHome != null where village.PlayerId == CurrentPlayerId select new { village, currentVillage } ).ToDictionaryAsync(d => d.village, d => d.currentVillage); var possibleCommands = await( from command in CurrentSets.Command .Include(c => c.Army) .Include(c => c.SourceVillage) where command.ArmyId != null where command.ReturnsAt > serverTime where !invalidVillageIds.Contains(command.SourceVillageId) select command ).ToListAsync(); var offensiveTypes = new[] { JSON.TroopType.Axe, JSON.TroopType.Light, JSON.TroopType.Heavy, JSON.TroopType.Ram, JSON.TroopType.Catapult }; var allInstructions = new ConcurrentDictionary <Scaffold.Command, List <Planning.CommandInstruction> >(); var ownVillagesById = ownVillages.Keys.ToDictionary(v => v.VillageId, v => v); bool MeetsMinimumPopulation(Scaffold.Command command) { var army = (JSON.Army)command.Army; return(army != null && 2000 < Model.Native.ArmyStats.CalculateTotalPopulation(army, offensiveTypes)); } var targetPlayerIdsTmp = new ConcurrentDictionary <long, byte>(); // Generate backtime plans try { var commands = possibleCommands.Where(MeetsMinimumPopulation); var parallelOptions = new ParallelOptions { MaxDegreeOfParallelism = Environment.ProcessorCount }; var planningTask = Parallel.ForEach(commands, parallelOptions, (command) => { var planner = new Planning.CommandOptionsCalculator(CurrentWorldSettings); planner.Requirements.Add(new Planning.Requirements.MaximumTravelTimeRequirement { MaximumTime = command.ReturnsAt.Value - serverTime }); planner.Requirements.Add(Planning.Requirements.MinimumOffenseRequirement.FractionalNuke(0.05f).LimitTroopType(offensiveTypes)); planner.Requirements.Add(Planning.Requirements.WithoutTroopTypeRequirement.WithoutNobles); var plan = planner.GenerateOptions(ownVillages, command.SourceVillage); if (plan.Count > 0) { targetPlayerIdsTmp.TryAdd(command.SourcePlayerId, 0); allInstructions.TryAdd(command, plan); } }); } catch (AggregateException e) { throw e.InnerException ?? e.InnerExceptions.First(); } // Find existing backtimes for commands that had plans generated var backtimedVillageIds = allInstructions.Keys.Select(c => c.SourceVillageId).ToList(); var commandsToBacktimedVillages = await( from command in CurrentSets.Command where backtimedVillageIds.Contains(command.TargetVillageId) where command.Army != null where command.LandsAt > serverTime select new { command.TargetVillageId, command.LandsAt, command.Army } ).ToListAsync(); var troopsAtBacktimedVillages = await( from village in CurrentSets.CurrentVillage where backtimedVillageIds.Contains(village.VillageId) select new { village.VillageId, village.ArmyStationed } ).ToDictionaryAsync(v => v.VillageId, v => v.ArmyStationed); var battleSimulator = new Features.Simulation.BattleSimulator(); var existingBacktimesPerCommand = allInstructions.Keys.ToDictionary(c => c.CommandId, id => 0); foreach (var command in commandsToBacktimedVillages) { Scaffold.Command backtimedCommand = null; var commandsReturningToVillage = allInstructions.Keys.Where(c => c.SourceVillageId == command.TargetVillageId); foreach (var returning in commandsReturningToVillage) { if (command.LandsAt > returning.ReturnsAt && (command.LandsAt - returning.ReturnsAt.Value).TotalSeconds < 10) { backtimedCommand = returning; } } if (backtimedCommand == null) { continue; } var backtimedArmy = (JSON.Army)backtimedCommand.Army; var battleResult = battleSimulator.SimulateAttack(command.Army, backtimedArmy, 20, CurrentWorldSettings.ArchersEnabled); var originalPopulation = (float)Model.Native.ArmyStats.CalculateTotalPopulation(backtimedArmy, offensiveTypes); var newPopulation = (float)Model.Native.ArmyStats.CalculateTotalPopulation(battleResult.DefendingArmy, offensiveTypes); var percentLost = 1 - newPopulation / originalPopulation; if (percentLost > 0.85f) { existingBacktimesPerCommand[backtimedCommand.CommandId]++; } } var targetPlayerIds = targetPlayerIdsTmp.Keys.ToList(); var playerInfoById = await( from player in CurrentSets.Player where targetPlayerIds.Contains(player.PlayerId) select new { player.PlayerId, player.TribeId, player.PlayerName } ).ToDictionaryAsync(i => i.PlayerId, i => i); var tribeIds = playerInfoById.Values.Select(i => i.TribeId).Where(t => t != null).Distinct(); var tribeInfoById = await( from tribe in CurrentSets.Ally where tribeIds.Contains(tribe.TribeId) select new { tribe.TribeId, tribe.Tag, tribe.TribeName } ).ToDictionaryAsync(i => i.TribeId, i => i); // Get JSON version of instructions and info for all backtimeable nukes var result = allInstructions.Select(commandInstructions => { (var command, var instructions) = commandInstructions.Tupled(); var backtimeInfo = new JSON.BacktimeInfo(); var targetVillage = command.SourceVillage; // "Target" is the source of the command var targetPlayer = playerInfoById[command.SourcePlayerId]; var targetTribe = targetPlayer.TribeId == null ? null : tribeInfoById[targetPlayer.TribeId.Value]; var isStacked = false; if (troopsAtBacktimedVillages[targetVillage.VillageId] != null) { var stationedTroops = troopsAtBacktimedVillages[targetVillage.VillageId]; var defensePower = Features.Simulation.BattleSimulator.TotalDefensePower(stationedTroops); isStacked = defensePower > 1000000; } // Gather instructions and info for this command return(new JSON.BacktimeInfo { // General info TravelingArmyPopulation = Model.Native.ArmyStats.CalculateTotalPopulation(command.Army, offensiveTypes), TargetPlayerName = targetPlayer.PlayerName.UrlDecode(), TargetTribeName = targetTribe?.TribeName?.UrlDecode(), TargetTribeTag = targetTribe?.Tag?.UrlDecode(), ExistingBacktimes = existingBacktimesPerCommand[command.CommandId], IsStacked = isStacked, // Convert backtime instructions to JSON format Instructions = instructions.Select(instruction => { var sourceVillage = ownVillagesById[instruction.SendFrom]; var sourceVillageArmy = (JSON.Army)ownVillages[sourceVillage].ArmyAtHome; var instructionArmy = sourceVillageArmy.BasedOn(instruction.TroopType); return new JSON.BattlePlanCommand { LandsAt = command.ReturnsAt.Value, LaunchAt = command.ReturnsAt.Value - instruction.TravelTime, TravelTimeSeconds = (int)instruction.TravelTime.TotalSeconds, TroopType = instruction.TroopType.ToString().ToLower(), CommandPopulation = Model.Native.ArmyStats.CalculateTotalPopulation(instructionArmy), CommandAttackPower = Features.Simulation.BattleSimulator.TotalAttackPower(instructionArmy), CommandDefensePower = Features.Simulation.BattleSimulator.TotalDefensePower(instructionArmy), SourceVillageId = instruction.SendFrom, TargetVillageId = instruction.SendTo, SourceVillageName = sourceVillage.VillageName.UrlDecode(), TargetVillageName = targetVillage.VillageName.UrlDecode(), SourceVillageX = sourceVillage.X.Value, SourceVillageY = sourceVillage.Y.Value, TargetVillageX = targetVillage.X.Value, TargetVillageY = targetVillage.Y.Value, }; }).ToList() }); }).ToList(); return(Ok(result)); }
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)); } }