Skip to content

Commit

Permalink
implement config property to set language for achievement names. Closes
Browse files Browse the repository at this point in the history
  • Loading branch information
Rudokhvist committed Mar 11, 2024
1 parent 32b1d29 commit 258cd7a
Show file tree
Hide file tree
Showing 5 changed files with 109 additions and 111 deletions.
4 changes: 2 additions & 2 deletions ASFAchievementManager/ASF-Achievement-Manager.csproj
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<Authors>Rudokhvist</Authors>
<AssemblyVersion>0.3.0.0</AssemblyVersion>
<AssemblyVersion>0.3.1.0</AssemblyVersion>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<LangVersion>latest</LangVersion>
Expand Down
42 changes: 36 additions & 6 deletions ASFAchievementManager/ASFAchievementManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,21 @@
using SteamKit2;
using System.Linq;
using System.Collections.Concurrent;
using System.Text.Json;
using System.Globalization;

namespace ASFAchievementManager {

[Export(typeof(IPlugin))]
public sealed class ASFAchievementManager : IBotSteamClient, IBotCommand2 {
public sealed class ASFAchievementManager : IBotSteamClient, IBotCommand2, IASF {
private static readonly ConcurrentDictionary<Bot, AchievementHandler> AchievementHandlers = new();
public string Name => "ASF Achievement Manager";
public Version Version => typeof(ASFAchievementManager).Assembly.GetName().Version ?? new Version("0");

public static CultureInfo? AchievementsCulture { get; private set; }

private static readonly char[] Separator = [','];

public Task OnLoaded() {
ASF.ArchiLogger.LogGenericInfo("ASF Achievement Manager Plugin by Rudokhvist, powered by ginger cats");
return Task.CompletedTask;
Expand Down Expand Up @@ -61,7 +68,7 @@ public Task OnLoaded() {
return null;
}

string[] gameIDs = appids.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
string[] gameIDs = appids.Split(Separator, StringSplitOptions.RemoveEmptyEntries);

if (gameIDs.Length == 0) {
return bot.Commands.FormatBotResponse(string.Format(Strings.ErrorIsEmpty, nameof(gameIDs)));
Expand All @@ -72,7 +79,7 @@ public Task OnLoaded() {
return null;
}

HashSet<uint> gamesToGetAchievements = new();
HashSet<uint> gamesToGetAchievements = [];

foreach (string game in gameIDs) {
if (!uint.TryParse(game, out uint gameID) || (gameID == 0)) {
Expand Down Expand Up @@ -133,9 +140,9 @@ public Task OnLoaded() {
return null;
}

HashSet<uint> achievements = new();
HashSet<uint> achievements = [];

string[] achievementStrings = achievementNumbers.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
string[] achievementStrings = achievementNumbers.Split(Separator, StringSplitOptions.RemoveEmptyEntries);

if (!achievementNumbers.Equals("*")) {
foreach (string achievement in achievementStrings) {
Expand Down Expand Up @@ -167,6 +174,29 @@ public Task OnLoaded() {
return responses.Count > 0 ? string.Join(Environment.NewLine, responses) : null;
}

}
public Task OnASFInit(IReadOnlyDictionary<string, JsonElement>? additionalConfigProperties = null) {
if (additionalConfigProperties != null) {
foreach (KeyValuePair<string, JsonElement> configProperty in additionalConfigProperties) {
switch (configProperty.Key) {
case "Rudokhvist.AchievementsCulture" when configProperty.Value.ValueKind == JsonValueKind.String: {
string configCulture = configProperty.Value.ToString();
try {
AchievementsCulture = CultureInfo.CreateSpecificCulture(configCulture);
} catch (Exception) {
AchievementsCulture = null;
ASF.ArchiLogger.LogGenericError(Strings.ErrorInvalidCurrentCulture);
}
ASF.ArchiLogger.LogGenericInfo("Culture for achievement names was set to " + configCulture);
break;
}

}
}
}
return Task.CompletedTask;
}


};

}
125 changes: 63 additions & 62 deletions ASFAchievementManager/AchievementHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,7 @@ internal abstract class AchievementsCallBack<T> : CallbackMsg {
internal readonly bool Success;

internal AchievementsCallBack(JobID jobID, T msg, Func<T, EResult> eresultGetter, string error) {
if (jobID == null) {
throw new ArgumentNullException(nameof(jobID));
}
ArgumentNullException.ThrowIfNull(jobID);

if (msg == null) {
throw new ArgumentNullException(nameof(msg));
Expand Down Expand Up @@ -70,58 +68,59 @@ internal SetAchievementsCallback(JobID jobID, CMsgClientStoreUserStatsResponse m

//Utilities

private List<StatData>? ParseResponse(CMsgClientGetUserStatsResponse Response) {
List<StatData> result = new List<StatData>();
KeyValue KeyValues = new KeyValue();
if (Response.schema != null) {
using (MemoryStream ms = new MemoryStream(Response.schema)) {
private static List<StatData>? ParseResponse(CMsgClientGetUserStatsResponse response) {
List<StatData> result = [];
KeyValue KeyValues = new();
if (response.schema != null) {
using (MemoryStream ms = new(response.schema)) {
if (!KeyValues.TryReadAsBinary(ms)) {
ASF.ArchiLogger.LogGenericError(string.Format(Strings.ErrorIsInvalid, nameof(Response.schema)));
ASF.ArchiLogger.LogGenericError(string.Format(Strings.ErrorIsInvalid, nameof(response.schema)));
return null;
};
}

//first we enumerate all real achievements
foreach (KeyValue stat in KeyValues.Children.Find(Child => Child.Name == "stats")?.Children ?? new List<KeyValue>()) {
if (stat.Children.Find(Child => Child.Name == "type")?.Value == "4") {
foreach (KeyValue Achievement in stat.Children.Find(Child => Child.Name == "bits")?.Children ?? new List<KeyValue>()) {
foreach (KeyValue stat in KeyValues.Children.Find(child => child.Name == "stats")?.Children ?? []) {
if (stat.Children.Find(child => child.Name == "type")?.Value == "4") {
foreach (KeyValue Achievement in stat.Children.Find(child => child.Name == "bits")?.Children ?? []) {
if (int.TryParse(Achievement.Name, out int bitNum)) {
if (uint.TryParse(stat.Name, out uint statNum)) {
uint? stat_value = Response?.stats?.Find(statElement => statElement.stat_id == statNum)?.stat_value;
uint? stat_value = response?.stats?.Find(statElement => statElement.stat_id == statNum)?.stat_value;
bool isSet = stat_value != null && (stat_value & ((uint) 1 << bitNum)) != 0;

bool restricted = Achievement.Children.Find(Child => Child.Name == "permission") != null;

string? dependancyName = (Achievement.Children.Find(Child => Child.Name == "progress") == null) ? "" : Achievement.Children.Find(Child => Child.Name == "progress")?.Children?.Find(Child => Child.Name == "value")?.Children?.Find(Child => Child.Name == "operand1")?.Value;

uint.TryParse((Achievement.Children.Find(Child => Child.Name == "progress") == null) ? "0" : Achievement.Children.Find(Child => Child.Name == "progress")!.Children.Find(Child => Child.Name == "max_val")?.Value, out uint dependancyValue);
string lang = CultureInfo.CurrentUICulture.EnglishName.ToLower();

Dictionary<string, string> countryLanguageMap = new()
{
{ "portuguese (brazil)", "brazilian" },
{ "korean", "koreana" },
{ "chinese (traditional)", "tchinese" },
{ "chinese (simplified)", "schinese" }
};

if (countryLanguageMap.ContainsKey(lang))
{
lang = countryLanguageMap[lang];
}
else
{
if (lang.IndexOf('(') > 0)
{
lang = lang.Substring(0, lang.IndexOf('(') - 1);
}
}
if (Achievement.Children.Find(Child => Child.Name == "display")?.Children?.Find(Child => Child.Name == "name")?.Children?.Find(Child => Child.Name == lang) == null)
{
lang = "english"; // Fallback
}

string? name = Achievement.Children.Find(Child => Child.Name == "display")?.Children?.Find(Child => Child.Name == "name")?.Children?.Find(Child => Child.Name == lang)?.Value;
bool restricted = Achievement.Children.Find(child => child.Name == "permission") != null;

string? dependancyName = (Achievement.Children.Find(child => child.Name == "progress") == null) ? "" : Achievement.Children.Find(child => child.Name == "progress")?.Children?.Find(child => child.Name == "value")?.Children?.Find(child => child.Name == "operand1")?.Value;

if (!uint.TryParse((Achievement.Children.Find(child => child.Name == "progress") == null) ? "0" : Achievement.Children.Find(child => child.Name == "progress")!.Children.Find(child => child.Name == "max_val")?.Value, out uint dependancyValue)) {
ASF.ArchiLogger.LogGenericError(string.Format(Strings.ErrorIsInvalid, nameof(dependancyValue)));
return null;
}

string lang = ASFAchievementManager.AchievementsCulture == null ?
CultureInfo.CurrentUICulture.EnglishName.ToLower() :
ASFAchievementManager.AchievementsCulture.EnglishName.ToLower();

Dictionary<string, string> countryLanguageMap = new()
{
{ "portuguese (brazil)", "brazilian" },
{ "korean", "koreana" },
{ "chinese (traditional)", "tchinese" },
{ "chinese (simplified)", "schinese" }
};

if (countryLanguageMap.TryGetValue(lang, out string? value)) {
lang = value;
} else {
if (lang.IndexOf('(') > 0) {
lang = lang[..(lang.IndexOf('(') - 1)];
}
}
if (Achievement.Children.Find(child => child.Name == "display")?.Children?.Find(child => child.Name == "name")?.Children?.Find(child => child.Name == lang) == null) {
lang = "english"; // Fallback
}

string? name = Achievement.Children.Find(child => child.Name == "display")?.Children?.Find(child => child.Name == "name")?.Children?.Find(child => child.Name == lang)?.Value;
result.Add(new StatData() {
StatNum = statNum,
BitNum = bitNum,
Expand All @@ -140,11 +139,11 @@ internal SetAchievementsCallback(JobID jobID, CMsgClientStoreUserStatsResponse m
}
}
//Now we update all dependancies
foreach (KeyValue stat in KeyValues.Children.Find(Child => Child.Name == "stats")?.Children ?? new List<KeyValue>()) {
if (stat.Children.Find(Child => Child.Name == "type")?.Value == "1") {
foreach (KeyValue stat in KeyValues.Children.Find(child => child.Name == "stats")?.Children ?? []) {
if (stat.Children.Find(child => child.Name == "type")?.Value == "1") {
if (uint.TryParse(stat.Name, out uint statNum)) {
bool restricted = stat.Children.Find(Child => Child.Name == "permission") != null;
string? name = stat.Children.Find(Child => Child.Name == "name")?.Value;
bool restricted = stat.Children.Find(child => child.Name == "permission") != null;
string? name = stat.Children.Find(child => child.Name == "name")?.Value;
if (name != null) {
StatData? ParentStat = result.Find(item => item.DependancyName == name);
if (ParentStat != null) {
Expand All @@ -161,7 +160,7 @@ internal SetAchievementsCallback(JobID jobID, CMsgClientStoreUserStatsResponse m
return result;
}

private IEnumerable<CMsgClientStoreUserStats2.Stats> GetStatsToSet(List<CMsgClientStoreUserStats2.Stats> statsToSet, StatData statToSet, bool set = true) {
private static IEnumerable<CMsgClientStoreUserStats2.Stats> GetStatsToSet(List<CMsgClientStoreUserStats2.Stats> statsToSet, StatData statToSet, bool set = true) {
if (statToSet == null) {
yield break; //it should never happen
}
Expand All @@ -175,11 +174,11 @@ internal SetAchievementsCallback(JobID jobID, CMsgClientStoreUserStatsResponse m
yield return currentstat;
}

uint statMask = ((uint) 1 << statToSet.BitNum);
uint statMask = (uint) 1 << statToSet.BitNum;
if (set) {
currentstat.stat_value = currentstat.stat_value | statMask;
currentstat.stat_value |= statMask;
} else {
currentstat.stat_value = currentstat.stat_value & ~statMask;
currentstat.stat_value &= ~statMask;
}
if (!string.IsNullOrEmpty(statToSet.DependancyName)) {
CMsgClientStoreUserStats2.Stats? dependancystat = statsToSet.Find(stat => stat.stat_id == statToSet.Dependancy);
Expand Down Expand Up @@ -207,7 +206,7 @@ internal async Task<string> GetAchievements(Bot bot, ulong gameID) {
return "Can't retrieve achievements for " + gameID.ToString();
}

List<string> responses = new List<string>();
List<string> responses = [];
List<StatData>? Stats = ParseResponse(response.Response);
if (Stats == null) {
bot.ArchiLogger.LogNullError(Stats);
Expand All @@ -228,16 +227,18 @@ internal async Task<string> SetAchievements(Bot bot, uint appId, HashSet<uint> a
return Strings.BotNotConnected;
}

List<string> responses = new List<string>();
List<string> responses = [];

GetAchievementsCallback? response = await GetAchievementsResponse(bot, appId);
if (response == null) {
bot.ArchiLogger.LogNullError(response);
return "Can't retrieve achievements for " + appId.ToString(); ;
return "Can't retrieve achievements for " + appId.ToString();
;
}

if (!response.Success) {
return "Can't retrieve achievements for " + appId.ToString(); ;
return "Can't retrieve achievements for " + appId.ToString();
;
}

if (response.Response == null) {
Expand All @@ -252,11 +253,11 @@ internal async Task<string> SetAchievements(Bot bot, uint appId, HashSet<uint> a
return "\u200B\n" + string.Join(Environment.NewLine, responses);
}

List<CMsgClientStoreUserStats2.Stats> statsToSet = new List<CMsgClientStoreUserStats2.Stats>();
List<CMsgClientStoreUserStats2.Stats> statsToSet = [];

if (achievements.Count == 0) { //if no parameters provided - set/reset all. Don't kill me Archi.
foreach (StatData stat in Stats.Where(s => !s.Restricted)){
statsToSet.AddRange(GetStatsToSet(statsToSet, stat, set));
foreach (StatData stat in Stats.Where(s => !s.Restricted)) {
statsToSet.AddRange(GetStatsToSet(statsToSet, stat, set));
}
} else {
foreach (uint achievement in achievements) {
Expand Down Expand Up @@ -284,7 +285,7 @@ internal async Task<string> SetAchievements(Bot bot, uint appId, HashSet<uint> a
if (responses.Count > 0) {
responses.Add("Trying to switch remaining achievements..."); //if some errors occured
}
ClientMsgProtobuf<CMsgClientStoreUserStats2> request = new ClientMsgProtobuf<CMsgClientStoreUserStats2>(EMsg.ClientStoreUserStats2) {
ClientMsgProtobuf<CMsgClientStoreUserStats2> request = new(EMsg.ClientStoreUserStats2) {
SourceJobID = Client.GetNextJobID(),
Body = {
game_id = (uint) appId,
Expand All @@ -308,7 +309,7 @@ internal async Task<string> SetAchievements(Bot bot, uint appId, HashSet<uint> a
return null;
}

ClientMsgProtobuf<CMsgClientGetUserStats> request = new ClientMsgProtobuf<CMsgClientGetUserStats>(EMsg.ClientGetUserStats) {
ClientMsgProtobuf<CMsgClientGetUserStats> request = new(EMsg.ClientGetUserStats) {
SourceJobID = Client.GetNextJobID(),
Body = {
game_id = gameID,
Expand Down
2 changes: 1 addition & 1 deletion ArchiSteamFarm
Submodule ArchiSteamFarm updated 262 files
Loading

0 comments on commit 258cd7a

Please sign in to comment.