public async Task <ActionResult> Post(string method, int?winner, string playerData, string data, float duration, int lastWave, string duelData, long?steamId, string secret_key) { if (!(await CheckIp() || ValidateSecretKey(secret_key))) { return(Json(new NoPermissionFailure())); } LoggingUtil.Log($"Called method {method}"); switch (method) { case PostMethods.SaveMatchData: return(await SaveMatchData(winner, playerData, duration, lastWave, duelData)); case PostMethods.UpdateUnitData: return(await UpdateUnitData(data)); case PostMethods.UpdateAbilityData: return(await UpdateAbilityData(data)); case PostMethods.UpdateBuilders: return(await UpdateBuilders(data)); case PostMethods.SavePlayerData: default: return(Json(new InvalidRequestFailure())); } }
private async Task <ActionResult> UpdateFractionStatistics() { if (!await CheckIp()) { return(Json(new NoPermissionFailure())); } try { var strategy = _db.Database.CreateExecutionStrategy(); await strategy.ExecuteAsync(async() => { using var transaction = await _db.Database.BeginTransactionAsync(); await UpdateFractionStatistics_Internal(); await _db.SaveChangesAsync(); lock (_dbLock) { transaction.Commit(); } }); LoggingUtil.Log("Fraction statistics have been updated."); return(Json(new { success = true })); } catch (Exception e) { LoggingUtil.Error("Failed to compute fraction statistics"); LoggingUtil.Error(e.StackTrace); return(Json(new { success = false })); } }
public static async Task <List <T1> > GetOrCreateAsync <T1, T2>( this DbContext db, IEnumerable <T2> ids, Func <T1, T2> identifierFunc, Func <T2, T1> initFunc, string property = null, bool exclusive = false, Func <IQueryable <T1>, IQueryable <T1> > query = null) where T1 : class { var idList = ids.ToList(); if (idList.Count == 0) { return(new List <T1>()); } // Gather basic info var dbSet = db.Set <T1>(); var entityType = db.Model.GetEntityTypes().First(t => t.ClrType == typeof(T1)); var tableName = entityType.GetTableName(); var discrProp = entityType.GetDiscriminatorProperty(); var discrName = entityType.GetDiscriminatorValue(); if (string.IsNullOrWhiteSpace(property)) { property = entityType.FindPrimaryKey().Properties[0].Name; } var idString = string.Join(", ", idList.Select(id => $"'{id}'")); var sql = $"SELECT * FROM {tableName} WHERE {property} IN ({idString})"; if (discrProp != null && exclusive) { sql += $" AND {discrProp.Name} = '{discrName}'"; } var sqlQuery = dbSet.FromSqlRaw(sql); if (query != null) { sqlQuery = query(sqlQuery); } var existingData = await sqlQuery.ToDictionaryAsync(o => identifierFunc(o), o => o); var result = new List <T1>(); foreach (var id in idList) { if (!existingData.ContainsKey(id)) { var newData = initFunc(id); existingData[id] = newData; dbSet.Add(newData); LoggingUtil.Log($"Added {tableName} with key {id}"); } result.Add(existingData[id]); } return(result); }
private async Task <ActionResult> UpdatePlayerProfiles() { if (!await CheckIp()) { return(Json(new NoPermissionFailure())); } int stepSize = 50; await _steamApi.UpdatePlayerInformation(await _db.Players.OrderByDescending(p => p.SteamId).Where(p => p.Avatar == null).Take(stepSize).Select(p => p.SteamId).ToListAsync()); LoggingUtil.Log($"{stepSize} Player profiles have been requested."); return(Json(new { success = true })); }
private async Task UpdateRanking(Models.RankingTypes type, bool asc) { string key = type + "|" + asc; _cache.Set(key, true, DateTimeOffset.Now.AddDays(1)); await _db.Database.ExecuteSqlCommandAsync($"DELETE FROM Rankings WHERE Type = {(int)type} AND Ascending = {(asc ? 1 : 0)}"); Console.WriteLine($"Cleared Ranking for {type} {asc}"); string sql; string sqlSelects = ""; string sqlJoins = "JOIN Matches AS m \n" + "ON m.MatchId = pm.MatchId \n"; string sqlOrderBy = ""; string sqlWheres = "WHERE m.IsTraining = FALSE \n"; switch (type) { case RankingTypes.EarnedTangos: sqlSelects = ", SUM(EarnedTangos) AS Gold \n"; sqlOrderBy = "ORDER BY Gold " + (asc ? "ASC" : "DESC") + " \n"; break; case RankingTypes.EarnedGold: sqlSelects = ", SUM(EarnedGold) AS Gold \n"; sqlOrderBy = "ORDER BY Gold " + (asc ? "ASC" : "DESC") + " \n"; break; case RankingTypes.Rating: default: sqlSelects = ", SUM(RatingChange) AS Rating \n"; sqlOrderBy = "ORDER BY Rating " + (asc ? "ASC" : "DESC") + " \n"; sqlJoins = ""; sqlWheres = ""; break; } sql = "INSERT INTO Rankings \n" + "(Type, Ascending, PlayerId, Position) \n" + $"SELECT @t := {(int)type}, @a := {(asc ? "TRUE" : "FALSE")}, PlayerId, @rownum := @rownum + 1 AS position\n" + "FROM (SELECT PlayerId \n" + sqlSelects + "FROM PlayerMatchData AS pm \n" + sqlJoins + sqlWheres + "GROUP BY pm.PlayerId \n" + sqlOrderBy + ") AS pr, \n" + "(SELECT @rownum := 0) AS r \n"; await _db.Database.ExecuteSqlCommandAsync(sql); LoggingUtil.Log("Ranking has been updated."); }
/// <summary> /// Verify the loaded assembly meets a minimum version number. /// </summary> /// <param name="name">Assembly name</param> /// <param name="version">Minium version</param> /// <param name="silent">Silent mode</param> /// <returns>The assembly if the version check was successful. If not, logs and error and returns null.</returns> public static Assembly VerifyAssemblyVersion(string name, string version, bool silent = false) { // Logic courtesy of DMagic var assemblies = AssemblyLoader.loadedAssemblies.Where(a => a.assembly.GetName().Name == name); var assembly = assemblies.FirstOrDefault(); if (assembly != null) { if (assemblies.Count() > 1) { LoggingUtil.LogWarning(typeof(ContractConfigurator), StringBuilderCache.Format("Multiple assemblies with name '{0}' found!", name)); } string receivedStr; // First try the informational version var ainfoV = Attribute.GetCustomAttribute(assembly.assembly, typeof(AssemblyInformationalVersionAttribute)) as AssemblyInformationalVersionAttribute; if (ainfoV != null) { receivedStr = ainfoV.InformationalVersion; } // If that fails, use the product version else { receivedStr = FileVersionInfo.GetVersionInfo(assembly.assembly.Location).ProductVersion; } // If that still fails, fall back on AssemblyVersion if (string.IsNullOrEmpty(receivedStr) || receivedStr == " ") { receivedStr = assembly.assembly.GetName().Version.ToString(); } System.Version expected = ParseVersion(version); System.Version received = ParseVersion(receivedStr); if (received >= expected) { LoggingUtil.LogVerbose(typeof(ContractConfigurator), "Version check for '{0}' passed. Minimum required is {1}, version found was {2}", name, version, receivedStr); return(assembly.assembly); } else { LoggingUtil.Log(silent ? LoggingUtil.LogLevel.DEBUG : LoggingUtil.LogLevel.ERROR, typeof(Version), "Version check for '{0}' failed! Minimum required is {1}, version found was {2}", name, version, receivedStr); return(null); } } else { LoggingUtil.Log(silent ? LoggingUtil.LogLevel.VERBOSE : LoggingUtil.LogLevel.ERROR, typeof(Version), "Couldn't find assembly for '{0}'!", name); return(null); } }
private async Task <ActionResult> UpdateUnitStatistics() { if (!await CheckIp()) { return(Json(new NoPermissionFailure())); } var units = await _db.Units.ToListAsync(); foreach (var unit in units) { await UpdateUnitStatistic(unit.Name); } LoggingUtil.Log("Unit statistics have been updated."); return(Json(new { success = true })); }
private async Task <bool> CheckIp() { var ipAddress = Request.HttpContext.Connection.RemoteIpAddress; var ranges = await GetDotaIpRanges(); foreach (var range in ranges) { if (range.IsInRange(ipAddress)) { LoggingUtil.Log($"Client {ipAddress} is in Range {range.Lower} - {range.Upper}"); return(true); } } // LoggingUtil.Warn($"Connection to {ipAddress} refused."); return(false); }
private async Task <ActionResult> UpdateFractionStatistics() { if (!await CheckIp()) { return(Json(new NoPermissionFailure())); } var fractions = await _db.Fractions.ToListAsync(); foreach (var fraction in fractions) { await UpdateFractionStatistic(fraction.Name); } await _db.SaveChangesAsync(); LoggingUtil.Log("Fraction statistics have been updated."); return(Json(new { success = true })); }
/// <summary> /// Verify the loaded assembly meets a minimum version number. /// </summary> /// <param name="name">Assembly name</param> /// <param name="version">Minium version</param> /// <param name="silent">Silent mode</param> /// <returns>The assembly if the version check was successful. If not, logs and error and returns null.</returns> public static Assembly VerifyAssemblyVersion(string name, string version, bool silent = false) { // Logic courtesy of DMagic var assembly = AssemblyLoader.loadedAssemblies.SingleOrDefault(a => a.assembly.GetName().Name == name); if (assembly != null) { string receivedStr; // First try the informational version var ainfoV = Attribute.GetCustomAttribute(assembly.assembly, typeof(AssemblyInformationalVersionAttribute)) as AssemblyInformationalVersionAttribute; if (ainfoV != null) { receivedStr = ainfoV.InformationalVersion; } // If that fails, use the product version else { receivedStr = FileVersionInfo.GetVersionInfo(assembly.assembly.Location).ProductVersion; } System.Version expected = ParseVersion(version); System.Version received = ParseVersion(receivedStr); if (received >= expected) { LoggingUtil.LogVerbose(typeof(ContractConfigurator), "Version check for '" + name + "' passed. Minimum required is " + version + ", version found was " + receivedStr); return(assembly.assembly); } else { LoggingUtil.Log(silent ? LoggingUtil.LogLevel.VERBOSE : LoggingUtil.LogLevel.ERROR, typeof(ContractConfigurator), "Version check for '" + name + "' failed! Minimum required is " + version + ", version found was " + receivedStr); return(null); } } else { LoggingUtil.Log(silent ? LoggingUtil.LogLevel.VERBOSE : LoggingUtil.LogLevel.ERROR, typeof(ContractConfigurator), "Couldn't find assembly for '" + name + "'!"); return(null); } }
/// <summary> /// Parses the child DATA nodes out of the given config node, and returns the parsed values back in dataValues. /// </summary> /// <param name="configNode">The ConfigNode to load child DATA nodes from.</param> /// <param name="obj">The ContractConfigurator object to load from.</param> /// <param name="dataValues"></param> /// <param name="uniquenessChecks"></param> /// <returns></returns> public bool ParseDataNodes(ConfigNode configNode, IContractConfiguratorFactory obj, Dictionary <string, ContractType.DataValueInfo> dataValues, Dictionary <string, UniquenessCheck> uniquenessChecks) { bool valid = true; foreach (ConfigNode data in ConfigNodeUtil.GetChildNodes(configNode, "DATA")) { Type type = null; bool requiredValue = true; bool hidden = true; bool isLiteral = false; string title = ""; ConfigNodeUtil.SetCurrentDataNode(null); valid &= ConfigNodeUtil.ParseValue <Type>(data, "type", x => type = x, obj); valid &= ConfigNodeUtil.ParseValue <bool>(data, "requiredValue", x => requiredValue = x, obj, true); valid &= ConfigNodeUtil.ParseValue <string>(data, "title", x => title = x, obj, ""); valid &= ConfigNodeUtil.ParseValue <bool>(data, "hidden", x => hidden = x, obj, false); valid &= ConfigNodeUtil.ParseValue <bool>(data, "isLiteral", x => isLiteral = x, obj, false); bool doneTitleWarning = false; UniquenessCheck uniquenessCheck = UniquenessCheck.NONE; // Backwards compatibility for Contract Configurator 1.8.3 if (data.HasValue("uniqueValue") || data.HasValue("activeUniqueValue")) { LoggingUtil.LogWarning(this, "The use of uniqueValue and activeUniqueValue is obsolete since Contract Configurator 1.9.0, use uniquenessCheck instead."); bool uniqueValue = false; bool activeUniqueValue = false; valid &= ConfigNodeUtil.ParseValue <bool>(data, "uniqueValue", x => uniqueValue = x, obj, false); valid &= ConfigNodeUtil.ParseValue <bool>(data, "activeUniqueValue", x => activeUniqueValue = x, obj, false); uniquenessCheck = activeUniqueValue ? UniquenessCheck.CONTRACT_ACTIVE : uniqueValue ? UniquenessCheck.CONTRACT_ALL : UniquenessCheck.NONE; } else { valid &= ConfigNodeUtil.ParseValue <UniquenessCheck>(data, "uniquenessCheck", x => uniquenessCheck = x, obj, UniquenessCheck.NONE); } ConfigNodeUtil.SetCurrentDataNode(this); if (type != null) { foreach (ConfigNode.Value pair in data.values) { string name = pair.name; if (name != "type" && name != "title" && name != "hidden" && name != "requiredValue" && name != "uniqueValue" && name != "activeUniqueValue" && name != "uniquenessCheck" && name != "isLiteral") { if (uniquenessCheck != UniquenessCheck.NONE) { uniquenessChecks[name] = uniquenessCheck; } object value = null; // Create the setter function Type actionType = typeof(Action <>).MakeGenericType(type); Delegate del = Delegate.CreateDelegate(actionType, value, typeof(DataNode).GetMethod("NullAction")); // Set the ParseValue method generic MethodInfo method = (isLiteral ? methodParseValueLiteral : methodParseValue).MakeGenericMethod(new Type[] { type }); // Invoke the ParseValue method if (isLiteral) { this[name] = method.Invoke(null, new object[] { data, name, false }); } else { valid &= (bool)method.Invoke(null, new object[] { data, name, del, obj }); } dataValues[name] = new ContractType.DataValueInfo(title, requiredValue, hidden, type); // Recommend a title if (!data.HasValue("title") && requiredValue && !IsDeterministic(name) && !hidden && !doneTitleWarning && !dataValues[name].IsIgnoredType()) { doneTitleWarning = true; LoggingUtil.Log(obj.minVersion >= ContractConfigurator.ENHANCED_UI_VERSION ? LoggingUtil.LogLevel.ERROR : LoggingUtil.LogLevel.WARNING, this, obj.ErrorPrefix() + ": " + name + ": The field 'title' is required in for data node values where 'requiredValue' is true. Alternatively, the attribute 'hidden' can be set to true (but be careful - this can cause player confusion if all lines for the contract type show as 'Met' and the contract isn't generating)."); // Error on newer versions of contract packs if (obj.minVersion >= ContractConfigurator.ENHANCED_UI_VERSION) { valid = false; } } } } } } return(valid); }
public async Task <ActionResult> SaveMatchData(int?winner, string playerDataString, float duration, int lastWave, string duelDataString) { if (!winner.HasValue || string.IsNullOrWhiteSpace(playerDataString)) { return(Json(new MissingArgumentFailure())); } try { var strategy = _db.Database.CreateExecutionStrategy(); var matchId = await strategy.ExecuteAsync(async() => { using var transaction = await _db.Database.BeginTransactionAsync(); //Creating Match Match match = new Match { Winner = winner.Value, Duration = duration, LastWave = lastWave, Date = DateTime.UtcNow, IsTraining = true, Duels = new List <Duel>(), PlayerData = new List <PlayerMatchData>() }; _db.Matches.Add(match); await _db.SaveChangesAsync(); using var loggingContext = new LoggingContext($"#{match.MatchId}"); LoggingUtil.Log($"Adding Match {match.MatchId}"); //Adding Duels int createdDuels = 0; if (duelDataString.TryToJson(out JsonDocument duelDocument) && duelDocument.RootElement.ValueKind == JsonValueKind.Object) { foreach (var duelProp in duelDocument.RootElement.EnumerateObject()) { var order = int.Parse(duelProp.Name); var time = duelProp.Value.GetFloatOrDefault("time"); var duelWinner = duelProp.Value.GetIntOrDefault("winner"); Duel duel = new Duel { Match = match, MatchId = match.MatchId, Order = order, Winner = duelWinner, TimeStamp = time }; match.Duels.Add(duel); _db.Duels.Add(duel); createdDuels += 1; } } else { LoggingUtil.Warn($"No duel data available for Game"); } await _db.SaveChangesAsync(); //Adding player Data var playerObjs = playerDataString.ToJsonElement(); var steamIds = playerObjs.EnumerateObject().Select(p => long.Parse(p.Name)).ToList(); var players = await _db.GetOrCreateAsync( steamIds, p => p.SteamId, steamId => new Player { SteamId = steamId, Matches = new List <PlayerMatchData>() }, query: players => players .Include(p => p.Matches) .ThenInclude(m => m.Match) ); // We request the match history to calculate the rating change try { var steamInfo = await _steamApi.RequestPlayerInformation(steamIds); players.ForEach(p => p.Update(steamInfo[p.SteamId])); } catch (Exception) { LoggingUtil.Warn("Couldn't retrieve steam info."); } await _db.SaveChangesAsync(); // Enter player match data var playerData = new List <PlayerMatchData>(); foreach (var(steamId, player) in steamIds.Zip(players)) { var data = playerObjs.GetProperty(steamId.ToString()); var rawUnitData = data.GetProperty("unit_data"); var unitData = new Dictionary <string, UnitData>(); if (rawUnitData.ValueKind == JsonValueKind.Object) { unitData = ExtractPlayerUnitData(rawUnitData); } else { LoggingUtil.Warn($"No unit data for {steamId}"); } var newData = new PlayerMatchData { Player = player, PlayerId = steamId, Match = match, MatchId = match.MatchId, Abandoned = data.GetBoolOrDefault("abandoned"), Team = data.GetIntOrDefault("team"), FractionName = data.GetValueOrDefault("fraction"), EarnedTangos = data.GetIntOrDefault("earned_tangos"), EarnedGold = data.GetIntOrDefault("earned_gold"), UnitData = unitData }; player.Matches.Add(newData); match.PlayerData.Add(newData); playerData.Add(newData); } // Retrieve exp for units var unitNames = playerData.SelectMany(p => p.UnitData.Object.Keys).Distinct().ToList(); var units = await _db.GetOrCreateAsync(unitNames, u => u.Name, name => CreateUnitOrBuilder(name)); var experiences = units.ToDictionary(u => u.Name, u => u.Experience); // Check whether this is a training match match.IsTraining = DecideIsTraining(match, playerData); // Update ratings and statistics foreach (var pd in playerData) { pd.CalculateStats(experiences); pd.RatingChange = pd.CalculateRatingChange(); // this requires having all player histories loaded } _db.PlayerMatchData.AddRange(playerData); await _db.SaveChangesAsync(); lock (_dbLock) { transaction.Commit(); } LoggingUtil.Log($"Succesfully saved; Wave {lastWave}; Duration {duration}; Players: {players.Count}; Duels: {createdDuels}; IsTraining: {match.IsTraining}"); return(match.MatchId); });
private bool ValidateSecretKey(string secretKey) { LoggingUtil.Log($"Received secret key: {secretKey}"); return(secretKey == this._dedicatedServerKey); }
private async Task UpdateRanking(RankingTypes type, bool asc) { string key = type + "|" + asc; _cache.Set(key, true, DateTime.UtcNow.AddDays(1)); var oldTimeout = _db.Database.GetCommandTimeout(); _db.Database.SetCommandTimeout(TimeSpan.FromHours(1)); try { var strategy = _db.Database.CreateExecutionStrategy(); await strategy.ExecuteAsync(async() => { using var transcation = await _db.Database.BeginTransactionAsync(); await _db.Database.ExecuteSqlRawAsync($"DELETE FROM Rankings WHERE Type = {(int)type} AND Ascending = {(asc ? 1 : 0)}"); LoggingUtil.Log($"Cleared Ranking for {type} {asc}"); string sql; string sqlJoins = "JOIN Matches AS m \n" + "ON m.MatchId = pm.MatchId \n"; string sqlWheres = "WHERE m.IsTraining = FALSE \n"; string sqlSelects, sqlOrderBy; switch (type) { case RankingTypes.EarnedTangos: sqlSelects = ", SUM(EarnedTangos) AS Gold \n"; sqlOrderBy = "ORDER BY Gold " + (asc ? "ASC" : "DESC") + " \n"; break; case RankingTypes.EarnedGold: sqlSelects = ", SUM(EarnedGold) AS Gold \n"; sqlOrderBy = "ORDER BY Gold " + (asc ? "ASC" : "DESC") + " \n"; break; case RankingTypes.Rating: default: sqlSelects = ", SUM(RatingChange) AS Rating \n"; sqlOrderBy = "ORDER BY Rating " + (asc ? "ASC" : "DESC") + " \n"; sqlJoins = ""; sqlWheres = ""; break; } sql = "INSERT INTO Rankings \n" + "(Type, Ascending, PlayerId, Position) \n" + $"SELECT @t := {(int)type}, @a := {(asc ? "TRUE" : "FALSE")}, PlayerId, @rownum := @rownum + 1 AS position\n" + "FROM (SELECT PlayerId \n" + sqlSelects + "FROM PlayerMatchData AS pm \n" + sqlJoins + sqlWheres + "GROUP BY pm.PlayerId \n" + sqlOrderBy + ") AS pr, \n" + "(SELECT @rownum := 0) AS r \n"; await _db.Database.ExecuteSqlRawAsync(sql); lock (_dbLock) { transcation.Commit(); } }); LoggingUtil.Log("Ranking has been updated."); } catch (Exception e) { LoggingUtil.Error(e.ToString()); LoggingUtil.Error("Failed to update ranking"); } finally { _db.Database.SetCommandTimeout(oldTimeout); } }