diff --git a/src/Controllers/Common/AchievementController.cs b/src/Controllers/Common/AchievementController.cs index 5bc48dc..b7ccab7 100644 --- a/src/Controllers/Common/AchievementController.cs +++ b/src/Controllers/Common/AchievementController.cs @@ -43,10 +43,10 @@ public class AchievementController : Controller { //[Produces("application/xml")] [Route("AchievementWebService.asmx/GetAllRanks")] public IActionResult GetAllRanks([FromForm] string apiKey) { - uint gameVersion = ClientVersion.GetVersion(apiKey); - if (gameVersion <= ClientVersion.Max_OldJS && (gameVersion & ClientVersion.WoJS) != 0) + uint gameID = ClientVersion.GetGameID(apiKey); + if (gameID == ClientVersion.WoJS) return Ok(XmlUtil.ReadResourceXmlString("ranks.allranks_wojs")); - if (gameVersion == ClientVersion.MB) + if (gameID == ClientVersion.MB) return Ok(XmlUtil.ReadResourceXmlString("ranks.allranks_mb")); return Ok(XmlUtil.ReadResourceXmlString("ranks.allranks_sod")); } diff --git a/src/Controllers/Common/ContentController.cs b/src/Controllers/Common/ContentController.cs index 3dbeeae..08168c6 100644 --- a/src/Controllers/Common/ContentController.cs +++ b/src/Controllers/Common/ContentController.cs @@ -529,8 +529,7 @@ public class ContentController : Controller { raisedPetData.PetTypeID = petTypeID; raisedPetData.RaisedPetID = 0; // Initially make zero, so the db auto-fills raisedPetData.EntityID = Guid.Parse(dragonId); - uint gameVersion = ClientVersion.GetVersion(apiKey); - if (gameVersion > ClientVersion.Max_OldJS || (gameVersion & ClientVersion.WoJS) == 0) + if (ClientVersion.GetGameID(apiKey) != ClientVersion.WoJS) raisedPetData.Name = string.Concat("Dragon-", dragonId.AsSpan(0, 8)); // Start off with a random name (if game isn't WoJS) raisedPetData.IsSelected = false; // The api returns false, not sure why raisedPetData.CreateDate = new DateTime(DateTime.Now.Ticks); @@ -1393,9 +1392,7 @@ public class ContentController : Controller { List allParties = ctx.Parties.ToList(); List userParties = new List(); - uint gameVersion = ClientVersion.GetVersion(apiKey); - if (gameVersion <= ClientVersion.Max_OldJS && (gameVersion & ClientVersion.WoJS) != 0) - gameVersion = ClientVersion.WoJS; // This can be optimized. It's also a shortcut. + uint gameID = ClientVersion.GetGameID(apiKey); foreach(var party in allParties) { if(DateTime.UtcNow >= party.ExpirationDate) @@ -1407,13 +1404,15 @@ public class ContentController : Controller { } // Only send parties to their respective games. - if (gameVersion != party.GameVersion) continue; + if (gameID != party.GameID) continue; Viking viking = ctx.Vikings.FirstOrDefault(e => e.Id == party.VikingId); AvatarData avatarData = XmlUtil.DeserializeXml(viking.AvatarSerialized); - UserParty userParty = new UserParty - { - DisplayName = avatarData.DisplayName, + + + + UserParty userParty = new UserParty { + DisplayName = $"{avatarData.DisplayName}'s {party.Descriptor} Party", UserName = avatarData.DisplayName, ExpirationDate = party.ExpirationDate, Icon = party.IconAsset, @@ -1423,21 +1422,6 @@ public class ContentController : Controller { UserID = viking.Uid }; - string location = party.Location switch { - "MyNeighborhood" => "Block ", - "MyPodInt" => "Pod ", - "MyLoungeSSInt" => "Lounge ", - _ => "" - }; - string type = party.PartyType switch { - "VIPRoom" => "VIP ", - "Birthday" => "Birthday ", - "NewYears" => "New Years ", - "Holiday" => "Holiday ", - _ => "" - }; - userParty.DisplayName = $"{userParty.UserName}'s {location}{type}Party"; // Spaces are included in the location and type when they aren't empty. - userParties.Add(userParty); } @@ -1503,85 +1487,40 @@ public class ContentController : Controller { return Ok(null); } - uint gameVersion = ClientVersion.GetVersion(apiKey); - if (gameVersion <= ClientVersion.Max_OldJS && (gameVersion & ClientVersion.WoJS) != 0) - gameVersion = ClientVersion.WoJS; // This can be optimized. It's also a shortcut. + uint gameID = ClientVersion.GetGameID(apiKey); - // create a party based on bought itemid - Party party = new Party { - PrivateParty = false, - GameVersion = gameVersion, - PartyType = partyType - }; + PartiesInfo data = XmlUtil.DeserializeXml(XmlUtil.ReadResourceXmlString("parties_info")); + PartyInfo? info = data.Parties.FirstOrDefault(p => p.GameID == gameID && p.Type == partyType); + if (info == null) return Ok(null); - switch (gameVersion) { - case ClientVersion.WoJS: - party.Location = "MyVIPRoomInt"; - party.IconAsset = "RS_DATA/Parties01.unity3d/IcoPartyDefault"; - party.LocationIconAsset = "RS_DATA/Parties01.unity3d/IcoPartyLocationVIPRoom"; - if (partyType == "VIPRoom") { - party.AssetBundle = "RS_DATA/PfMyVIPRoomIntPartyGroup.unity3d/PfMyVIPRoomIntPartyGroup"; - } else if (partyType == "Holiday") { - party.IconAsset = "RS_DATA/Parties01.unity3d/IcoPartyDefault"; - party.AssetBundle = "RS_DATA/PfMyVIPRoomIntXmasGroup.unity3d/PfMyVIPRoomIntXmasGroup"; - } else { - party.Location = "MyNeighborhood"; - party.LocationIconAsset = "RS_DATA/Parties01.unity3d/IcoPartyLocationMyNeighborhood"; - party.AssetBundle = "RS_DATA/PfMyNeighborhoodParty.unity3d/PfMyNeighborhoodParty"; - } - break; - case ClientVersion.MB: - party.Location = "MyPodInt"; - if (partyType == "Birthday") { - party.IconAsset = "RS_DATA/PfUiPartiesListMB.unity3d/IcoMbPartyBirthday"; - party.AssetBundle = "RS_DATA/PfMyPodBirthdayParty.unity3d/PfMyPodBirthdayParty"; - } else { - party.IconAsset = "RS_DATA/PfUiPartiesListMB.unity3d/IcoMbPartyDefault"; - party.AssetBundle = "RS_DATA/PfMyPodParty.unity3d/PfMyPodParty"; - } - break; - case ClientVersion.SS: - party.Location = "MyLoungeSSInt"; - switch (partyType) { - case "Birthday": - party.IconAsset = "RS_DATA/PfUiPartiesListMB.unity3d/IcoSSStorePartyBirthdayDefault"; - party.AssetBundle = "RS_DATA/PfSsRoomInteriorBirthdayGroup.unity3d/PfSsRoomInteriorBirthdayGroup"; - break; - case "NewYears": - party.IconAsset = "RS_DATA/PfUiPartiesListMB.unity3d/IcoSSStorePartyNewYearDefault"; - party.AssetBundle = "RS_DATA/PfSsRoomInteriorNewYearGroup.unity3d/PfSsRoomInteriorNewYearGroup"; - break; - case "Holiday": - party.IconAsset = "RS_DATA/PfUiPartiesListMB.unity3d/IcoSSStorePartyHolidayDefault"; - party.AssetBundle = "RS_DATA/PfSsRoomInteriorHolidayGroup.unity3d/PfSsRoomInteriorHolidayGroup"; - break; - default: - party.IconAsset = "RS_DATA/PfUiPartiesListMB.unity3d/IcoSSStorePartyDefault"; - party.AssetBundle = "RS_DATA/PfSsRoomInteriorPartyGroup.unity3d/PfSsRoomInteriorPartyGroup"; - break; - } - break; - } - - - if (party.Location == null) { - Console.WriteLine($"Unsupported partyType \"{partyType}\" for gameversion 0x{gameVersion:X8}"); + if (info.Location == null) { + Console.WriteLine($"Unsupported partyType \"{partyType}\" for gameid 0x{gameID:X8}"); return Ok(null); } - party.ExpirationDate = DateTime.UtcNow.AddMinutes( - Int32.Parse(itemData.Attribute.FirstOrDefault(a => a.Key == "Time").Value) - ); - // check if party already exists - if (viking.Parties.FirstOrDefault(e => e.Location == party.Location) != null) return Ok(null); + if (viking.Parties.Any(e => e.Location == info.Location)) return Ok(null); + + // create a party based on bought itemid + Party party = new Party { + Location = info.Location, + IconAsset = info.Icon, + LocationIconAsset = data.LocationIcons.GetValueOrDefault(info.Location, ""), + AssetBundle = info.Bundle, + PrivateParty = false, + GameID = gameID, + ExpirationDate = DateTime.UtcNow.AddMinutes( + Int32.Parse(itemData.Attribute.FirstOrDefault(a => a.Key == "Time").Value) + ), + Descriptor = info.Descriptor + }; // take away coins viking.AchievementPoints.FirstOrDefault(e => e.Type == (int)AchievementPointTypes.GameCurrency)!.Value -= itemData.Cost; viking.Parties.Add(party); ctx.SaveChanges(); - + return Ok(true); } @@ -2187,7 +2126,7 @@ public class ContentController : Controller { [Route("ContentWebService.asmx/GetPeriodicGameDataByGame")] // used by Math Blaster and WoJS (probably from 24 hours ago to now) [VikingSession(UseLock = true)] public IActionResult GetPeriodicGameDataByGame(Viking viking, [FromForm] int gameId, bool isMultiplayer, int difficulty, int gameLevel, string key, int count, bool AscendingOrder, int score, bool buddyFilter, string apiKey) { - return Ok(gameDataService.GetGameData(viking, gameId, isMultiplayer, difficulty, gameLevel, key, count, AscendingOrder, buddyFilter, apiKey, DateTime.Now.AddHours(-24), DateTime.Now)); + return Ok(gameDataService.GetDailyGameData(viking, gameId, isMultiplayer, difficulty, gameLevel, key, count, AscendingOrder, buddyFilter, apiKey)); } [HttpPost] diff --git a/src/Controllers/Common/ItemStoreController.cs b/src/Controllers/Common/ItemStoreController.cs index f7bb205..dc7e0c5 100644 --- a/src/Controllers/Common/ItemStoreController.cs +++ b/src/Controllers/Common/ItemStoreController.cs @@ -77,10 +77,10 @@ public class ItemStoreController : Controller { public IActionResult GetAnnouncements([FromForm] string apiKey, [FromForm] int worldObjectID) { // TODO: This is a placeholder, although this endpoint seems to be only used to send announcements to the user (such as the server shutdown), so this might be sufficient. - uint gameVersion = ClientVersion.GetVersion(apiKey); - if (gameVersion <= ClientVersion.Max_OldJS && (gameVersion & ClientVersion.WoJS) != 0) { + uint gameID = ClientVersion.GetGameID(apiKey); + if (gameID == ClientVersion.WoJS) { return Ok(XmlUtil.DeserializeXml(XmlUtil.ReadResourceXmlString("announcements_wojs"))); - } else if (gameVersion == ClientVersion.SS && worldObjectID == 6) { + } else if (gameID == ClientVersion.SS && worldObjectID == 6) { return Ok(XmlUtil.DeserializeXml(XmlUtil.ReadResourceXmlString("announcements_ss"))); } diff --git a/src/Model/GameDataPair.cs b/src/Model/GameDataPair.cs index 9543936..7e029e5 100644 --- a/src/Model/GameDataPair.cs +++ b/src/Model/GameDataPair.cs @@ -7,5 +7,6 @@ public class GameDataPair { public int GameDataId { get; set; } public string Name { get; set; } = null!; public int Value { get; set; } + public int DailyValue { get; set; } public virtual GameData GameData { get; set; } = null!; } diff --git a/src/Model/Party.cs b/src/Model/Party.cs index 4378005..3cda319 100644 --- a/src/Model/Party.cs +++ b/src/Model/Party.cs @@ -15,7 +15,7 @@ namespace sodoff.Model public string LocationIconAsset { get; set; } = null!; public string AssetBundle { get; set; } = null!; public virtual Viking? Viking { get; set; } - public uint GameVersion { get; set; } = 0!; - public string PartyType { get; set; } = null!; + public uint? GameID { get; set; } + public string? Descriptor { get; set; } } } diff --git a/src/Resources/parties_info.xml b/src/Resources/parties_info.xml new file mode 100644 index 0000000..13e87ab --- /dev/null +++ b/src/Resources/parties_info.xml @@ -0,0 +1,90 @@ + + + + MyNeighborhood + RS_DATA/Parties01.unity3d/IcoPartyLocationMyNeighborhood + + + MyVIPRoomInt + RS_DATA/Parties01.unity3d/IcoPartyLocationVIPRoom + + + + + 0x01000000 + MyNeighborhood + Default + RS_DATA/Parties01.unity3d/IcoPartyDefault + RS_DATA/PfMyNeighborhoodParty.unity3d/PfMyNeighborhoodParty + Block + + + + 0x01000000 + MyVIPRoomInt + VIPRoom + RS_DATA/Parties01.unity3d/IcoPartyDefault + RS_DATA/PfMyVIPRoomIntPartyGroup.unity3d/PfMyVIPRoomIntPartyGroup + VIP + + + 0x01000000 + MyVIPRoomInt + Holiday + RS_DATA/Parties01.unity3d/IcoStorePartyXmasDefault + RS_DATA/PfMyVIPRoomIntXmasGroup.unity3d/PfMyVIPRoomIntXmasGroup + Holiday + + + + + 0x08000000 + MyPodInt + Default + RS_DATA/PfUiPartiesListMB.unity3d/IcoMbPartyDefault + RS_DATA/PfUiPartiesListMB.unity3d/IcoMbPartyDefault + Pod + + + 0x08000000 + MyPodInt + Birthday + RS_DATA/PfUiPartiesListMB.unity3d/IcoMbPartyBirthday + RS_DATA/PfMyPodBirthdayParty.unity3d/PfMyPodBirthdayParty + Birthday + + + + + 0x02000000 + MyLoungeSSInt + Default + RS_DATA/SSParties.unity3d/IcoSSStorePartyDefault + RS_DATA/PfSsRoomInteriorPartyGroup.unity3d/PfSsRoomInteriorPartyGroup + Lounge + + + 0x02000000 + MyLoungeSSInt + Birthday + RS_DATA/SSParties.unity3d/IcoSSStorePartyBirthdayDefault + RS_DATA/PfSsRoomInteriorBirthdayGroup.unity3d/PfSsRoomInteriorBirthdayGroup + Birthday + + + 0x02000000 + MyLoungeSSInt + NewYears + RS_DATA/SSParties.unity3d/IcoSSStorePartyNewYearDefault + RS_DATA/PfSsRoomInteriorNewYearGroup.unity3d/PfSsRoomInteriorNewYearGroup + New Years + + + 0x02000000 + MyLoungeSSInt + Holiday + RS_DATA/SSParties.unity3d/IcoSSStorePartyHolidayDefault + RS_DATA/PfSsRoomInteriorHolidayGroup.unity3d/PfSsRoomInteriorHolidayGroup + Holiday + + \ No newline at end of file diff --git a/src/Schema/PartiesInfo.cs b/src/Schema/PartiesInfo.cs new file mode 100644 index 0000000..4228387 --- /dev/null +++ b/src/Schema/PartiesInfo.cs @@ -0,0 +1,54 @@ +using sodoff.Util; +using System.Xml.Serialization; + +namespace sodoff.Schema; + +[XmlRoot(ElementName = "PartiesInfo", Namespace = "")] +[Serializable] +public class PartiesInfo { + [XmlElement(ElementName = "Location")] + public PartyIconData[] IconData { + get { return LocationIcons.Select(i => new PartyIconData {Name = i.Key, Icon = i.Value}).ToArray(); } + set { LocationIcons = value.ToDictionary(i => i.Name, i => i.Icon); } + } + + [XmlIgnore] + public Dictionary LocationIcons; + + [XmlElement(ElementName = "Party")] + public PartyInfo[] Parties; +} + +public class PartyIconData { + [XmlElement("Name")] + public string Name; + + [XmlElement("Icon")] + public string Icon; +} + +public class PartyInfo { + [XmlElement("Version")] + public string Version { + get { return GameID.ToString("X"); } + set { GameID = XmlUtil.HexToUint(value); } + } + + [XmlIgnore] + public uint GameID; + + [XmlElement("Location")] + public string Location; + + [XmlElement("Type")] + public string Type; + + [XmlElement("Icon")] + public string Icon; + + [XmlElement("AssetBundle")] + public string Bundle; + + [XmlElement("Descriptor")] + public string? Descriptor; +} \ No newline at end of file diff --git a/src/Services/AchievementStoreSingleton.cs b/src/Services/AchievementStoreSingleton.cs index 17a02f1..d3a56e3 100644 --- a/src/Services/AchievementStoreSingleton.cs +++ b/src/Services/AchievementStoreSingleton.cs @@ -73,14 +73,14 @@ namespace sodoff.Services { } public ReadOnlyDictionary GetAchievementsGroupIdToTaskId(uint gameVersion) { - gameVersion = GameVersionForTasks(gameVersion); + gameVersion = ClientVersion.GetGameID(gameVersion); if (achievementsTasks.ContainsKey(gameVersion)) return achievementsTasks[gameVersion].achievementsGroupIdToTaskId; return new ReadOnlyDictionary(new Dictionary()); } public List? GetAllAchievementTaskInfo(int taskID, uint gameVersion) { - gameVersion = GameVersionForTasks(gameVersion); + gameVersion = ClientVersion.GetGameID(gameVersion); if (achievementsTasks.ContainsKey(gameVersion) && achievementsTasks[gameVersion].achievementsRewardByTask.ContainsKey(taskID)) { return achievementsTasks[gameVersion].achievementsRewardByTask[taskID]; } @@ -121,13 +121,5 @@ namespace sodoff.Services { } return dragonXP; } - - private uint GameVersionForTasks(uint gameVersion) { - // all SoD version of SoD using the same Tasks database - if ((gameVersion & ClientVersion.Min_SoD) == 0xa0000000) return ClientVersion.Min_SoD; - // all version of WoJS (including lands) using the same Tasks database - if (gameVersion <= ClientVersion.Max_OldJS && (gameVersion & ClientVersion.WoJS) != 0) return ClientVersion.WoJS; - return gameVersion; - } } } diff --git a/src/Services/GameDataService.cs b/src/Services/GameDataService.cs index 8814586..2faa29a 100644 --- a/src/Services/GameDataService.cs +++ b/src/Services/GameDataService.cs @@ -31,9 +31,11 @@ public class GameDataService { viking.GameData.Add(gameData); } - gameData.DatePlayed = DateTime.UtcNow; + SavePairs(gameData, xmlDocumentData); + gameData.DatePlayed = DateTime.UtcNow; ctx.SaveChanges(); + return true; } @@ -70,6 +72,33 @@ public class GameDataService { return GetSummaryFromResponse(viking, isMultiplayer, difficulty, gameLevel, key, selectedData); } + + public GameDataSummary GetDailyGameData(Viking viking, int gameId, bool isMultiplayer, int difficulty, int gameLevel, string key, int count, bool AscendingOrder, bool buddyFilter, string apiKey) { + IQueryable query = ctx.GameData + .Where(x => + x.GameId == gameId && x.IsMultiplayer == false && + x.Difficulty == difficulty && x.GameLevel == gameLevel && + x.DatePlayed.Date == DateTime.UtcNow.Date + ).SelectMany(e => e.GameDataPairs).Where(x => x.Name == key); + + // TODO: Buddy filter + + if (AscendingOrder) query = query.OrderBy(e => e.Value); + else query = query.OrderByDescending(e => e.Value); + + List selectedData; + if (ClientVersion.GetVersion(apiKey) <= ClientVersion.Max_OldJS) + // use DisplayName instead of Name + selectedData = query.Select(e => new GameDataResponse( + XmlUtil.DeserializeXml(e.GameData.Viking.AvatarSerialized).DisplayName, e.GameData.Viking.Uid, e.GameData.DatePlayed, false, false, e.DailyValue) + ).Take(count).ToList(); + else + selectedData = query.Select(e => new GameDataResponse( + e.GameData.Viking.Name, e.GameData.Viking.Uid, e.GameData.DatePlayed, false, false, e.DailyValue) + ).Take(count).ToList(); + + return GetSummaryFromResponse(viking, isMultiplayer, difficulty, gameLevel, key, selectedData); + } // ByUser for JumpStart's My Scores public GameDataSummary GetGameDataByUser(Viking viking, int gameId, bool isMultiplayer, int difficulty, int gameLevel, string key, int count, bool AscendingOrder, string apiKey) { @@ -141,12 +170,20 @@ public class GameDataService { private void SavePairs(Model.GameData gameData, string xmlDocumentData) { foreach (var pair in GetGameDataPairs(xmlDocumentData)) { GameDataPair? dbPair = gameData.GameDataPairs.FirstOrDefault(x => x.Name == pair.Name); - if (dbPair == null) + + // If Name == "time" then (existing <= incoming) needs to be false (effectively (existing > incoming), as time should function). + // if Name is anything else, then second condition as normal. + bool newBest = (dbPair == null) || ((pair.Name == "time") != (dbPair.Value <= pair.Value)); + + if (dbPair == null) { gameData.GameDataPairs.Add(pair); - else if (pair.Name == "time" && dbPair.Value > pair.Value) - dbPair.Value = pair.Value; - else if (pair.Name != "time" && dbPair.Value <= pair.Value) - dbPair.Value = pair.Value; + dbPair = pair; + } else if (newBest) dbPair.Value = pair.Value; + + if ( + newBest || // Surpassed Score (or Unset) + gameData.DatePlayed.Date != DateTime.UtcNow.Date // Another Day + ) dbPair.DailyValue = pair.Value; } } diff --git a/src/Util/ClientVersion.cs b/src/Util/ClientVersion.cs index d1f0702..1fbb25f 100644 --- a/src/Util/ClientVersion.cs +++ b/src/Util/ClientVersion.cs @@ -13,6 +13,17 @@ public class ClientVersion { public const uint WoJS_StoryLand = 0x01000400; // World of JumpStart -- Storyland public const uint WoJS_NewAvatar = 0x01010000; // World of JumpStart with new avatars (e.g. 1.21) + public static uint GetGameID(uint gameVersion) { + if (gameVersion > ClientVersion.Max_OldJS) + return gameVersion & 0xf0000000; + else + return gameVersion & 0x0f000000; + } + + public static uint GetGameID(string apiKey) { + return GetGameID(GetVersion(apiKey)); + } + public static uint GetVersion(string apiKey) { if ( apiKey == "b99f695c-7c6e-4e9b-b0f7-22034d799013" || // PC / Windows diff --git a/src/sodoff.csproj b/src/sodoff.csproj index b8fe201..71af2cd 100644 --- a/src/sodoff.csproj +++ b/src/sodoff.csproj @@ -145,6 +145,9 @@ PreserveNewest + + PreserveNewest + PreserveNewest