From 406ebe20c2a5bcf774347a4fc021efd0ed23243f Mon Sep 17 00:00:00 2001 From: rpaciorek Date: Mon, 14 Aug 2023 16:34:14 +0000 Subject: [PATCH] initial support for XP points (#16) * GetPetAchievementsByUserID and enum for PointTypes * add RankService * use AchievementPointTypes for PointTypeID ... insted of int in schema * support for player XP, fix dragon XP - database table - return correct value in API call - save XP from mission * rename RankService to AchievementService * use addAchievementPoints for all non item reward this could be a good place for wallet servicing too ... so currency reward too * return const XP value for farming and fishing we don't have gathering method for those XPs yet * fix avatar schema, fix coding style --- .../Common/AchievementController.cs | 68 +++++++++++-------- src/Controllers/Common/ContentController.cs | 2 +- src/Controllers/Common/ProfileController.cs | 26 ++----- src/Model/AchievementPoints.cs | 12 ++++ src/Model/DBContext.cs | 10 +++ src/Model/Dragon.cs | 2 + src/Model/Viking.cs | 1 + src/Program.cs | 1 + src/Schema/AchievementPointTypes.cs | 35 ++++++++++ src/Schema/AchievementReward.cs | 2 +- src/Schema/ArrayOfUserRank.cs | 11 +++ src/Schema/AvatarData.cs | 1 + src/Schema/ItemStateCriteriaReplenishable.cs | 2 +- src/Schema/RewardMultiplier.cs | 2 +- src/Schema/UserAchievementInfo.cs | 2 +- src/Schema/UserRank.cs | 20 ++++++ src/Services/AchievementService.cs | 56 +++++++++++++++ src/Services/MissionService.cs | 7 +- src/Services/RoomService.cs | 2 +- 19 files changed, 207 insertions(+), 55 deletions(-) create mode 100644 src/Model/AchievementPoints.cs create mode 100644 src/Schema/AchievementPointTypes.cs create mode 100644 src/Schema/ArrayOfUserRank.cs create mode 100644 src/Schema/UserRank.cs create mode 100644 src/Services/AchievementService.cs diff --git a/src/Controllers/Common/AchievementController.cs b/src/Controllers/Common/AchievementController.cs index 0838e4f..a7c3ab3 100644 --- a/src/Controllers/Common/AchievementController.cs +++ b/src/Controllers/Common/AchievementController.cs @@ -4,22 +4,42 @@ using Microsoft.AspNetCore.Mvc; using sodoff.Attributes; using sodoff.Model; using sodoff.Schema; +using sodoff.Services; using sodoff.Util; namespace sodoff.Controllers.Common; public class AchievementController : Controller { private readonly DBContext ctx; - public AchievementController(DBContext ctx) { + private AchievementService achievementService; + public AchievementController(DBContext ctx, AchievementService achievementService) { this.ctx = ctx; + this.achievementService = achievementService; } [HttpPost] - //[Produces("application/xml")] + [Produces("application/xml")] [Route("AchievementWebService.asmx/GetPetAchievementsByUserID")] - public IActionResult GetPetAchievementsByUserID() { - // TODO, this is a placeholder - return Ok("\n"); + public IActionResult GetPetAchievementsByUserID([FromForm] string apiToken, [FromForm] string userId) { + // TODO: check session + + Viking? viking = ctx.Vikings.FirstOrDefault(e => e.Id == userId); + if (viking is null) { + return null; + } + + List dragonsAchievement = new List(); + foreach (Dragon dragon in viking.Dragons) { + dragonsAchievement.Add( + achievementService.CreateUserAchievementInfo(dragon.EntityId, dragon.PetXP, AchievementPointTypes.DragonXP) + ); + } + + ArrayOfUserAchievementInfo arrAchievements = new ArrayOfUserAchievementInfo { + UserAchievementInfo = dragonsAchievement.ToArray() + }; + + return Ok(arrAchievements); } [HttpPost] @@ -50,27 +70,19 @@ public class AchievementController : Controller { [Produces("application/xml")] [Route("AchievementWebService.asmx/GetAchievementsByUserID")] public IActionResult GetAchievementsByUserID([FromForm] string userId) { - // TODO: this is a placeholder + // TODO: check session + + Viking? viking = ctx.Vikings.FirstOrDefault(e => e.Id == userId); + + if (viking is null) { + return null; + } + ArrayOfUserAchievementInfo arrAchievements = new ArrayOfUserAchievementInfo { UserAchievementInfo = new UserAchievementInfo[]{ - new UserAchievementInfo { - UserID = Guid.Parse(userId), - AchievementPointTotal = 5000, - RankID = 30, - PointTypeID = 1 - }, - new UserAchievementInfo { - UserID = Guid.Parse(userId), - AchievementPointTotal = 5000, - RankID = 30, - PointTypeID = 9 - }, - new UserAchievementInfo { - UserID = Guid.Parse(userId), - AchievementPointTotal = 5000, - RankID = 30, - PointTypeID = 10 - }, + achievementService.CreateUserAchievementInfo(viking, AchievementPointTypes.PlayerXP), + achievementService.CreateUserAchievementInfo(viking.Id, 60000, AchievementPointTypes.PlayerFarmingXP), // TODO: placeholder until there is no leveling for farm XP + achievementService.CreateUserAchievementInfo(viking.Id, 20000, AchievementPointTypes.PlayerFishingXP), // TODO: placeholder until there is no leveling for fishing XP } }; @@ -86,7 +98,7 @@ public class AchievementController : Controller { return Ok(new AchievementReward[1] { new AchievementReward { Amount = 5, - PointTypeID = 5, + PointTypeID = AchievementPointTypes.CashCurrency, EntityID = Guid.Parse(viking.Id), EntityTypeID = 1, RewardID = 552 @@ -94,7 +106,7 @@ public class AchievementController : Controller { }); } - [HttpPost] + [HttpPost] [Produces("application/xml")] [Route("V2/AchievementWebService.asmx/SetUserAchievementTask")] [DecryptRequest("achievementTaskSetRequest")] @@ -112,7 +124,7 @@ public class AchievementController : Controller { AchievementRewards = new AchievementReward[1] { new AchievementReward { Amount = 25, - PointTypeID = 1, + PointTypeID = AchievementPointTypes.PlayerXP, RewardID = 910, EntityTypeID =1 } @@ -130,7 +142,7 @@ public class AchievementController : Controller { return Ok(new AchievementReward[1] { new AchievementReward { Amount = 25, - PointTypeID = 1, + PointTypeID = AchievementPointTypes.PlayerXP, EntityID = Guid.Parse(viking.Id), EntityTypeID = 1, RewardID = 552 diff --git a/src/Controllers/Common/ContentController.cs b/src/Controllers/Common/ContentController.cs index c3f6606..be13269 100644 --- a/src/Controllers/Common/ContentController.cs +++ b/src/Controllers/Common/ContentController.cs @@ -428,7 +428,7 @@ public class ContentController : Controller { return null; } - RaisedPetData[] dragons = viking.Dragons + RaisedPetData[] dragons = viking.Dragons // TODO (multiplayer) we should use userId .Where(d => d.RaisedPetData is not null) .Select(GetRaisedPetDataFromDragon) .ToArray(); diff --git a/src/Controllers/Common/ProfileController.cs b/src/Controllers/Common/ProfileController.cs index 4f8df71..761d36f 100644 --- a/src/Controllers/Common/ProfileController.cs +++ b/src/Controllers/Common/ProfileController.cs @@ -2,14 +2,17 @@ using Microsoft.AspNetCore.Mvc; using sodoff.Model; using sodoff.Schema; +using sodoff.Services; using sodoff.Util; namespace sodoff.Controllers.Common; public class ProfileController : Controller { private readonly DBContext ctx; - public ProfileController(DBContext ctx) { + private AchievementService achievementService; + public ProfileController(DBContext ctx, AchievementService achievementService) { this.ctx = ctx; + this.achievementService = achievementService; } [HttpPost] @@ -141,24 +144,9 @@ public class ProfileController : Controller { RankID = 0, // placeholder AchievementInfo = null, // placeholder Achievements = new UserAchievementInfo[] { - new UserAchievementInfo { - UserID = Guid.Parse(viking.Id), - AchievementPointTotal = 5000, - RankID = 30, - PointTypeID = 1 - }, - new UserAchievementInfo { - UserID = Guid.Parse(viking.Id), - AchievementPointTotal = 5000, - RankID = 30, - PointTypeID = 9 - }, - new UserAchievementInfo { - UserID = Guid.Parse(viking.Id), - AchievementPointTotal = 5000, - RankID = 30, - PointTypeID = 10 - }, + achievementService.CreateUserAchievementInfo(viking, AchievementPointTypes.PlayerXP), + achievementService.CreateUserAchievementInfo(viking.Id, 60000, AchievementPointTypes.PlayerFarmingXP), // TODO: placeholder until there is no leveling for farm XP + achievementService.CreateUserAchievementInfo(viking.Id, 20000, AchievementPointTypes.PlayerFishingXP), // TODO: placeholder until there is no leveling for fishing XP } }; diff --git a/src/Model/AchievementPoints.cs b/src/Model/AchievementPoints.cs new file mode 100644 index 0000000..9dae51b --- /dev/null +++ b/src/Model/AchievementPoints.cs @@ -0,0 +1,12 @@ +using System.ComponentModel.DataAnnotations; + +namespace sodoff.Model; +public class AchievementPoints { + public string VikingId { get; set; } + + public int Type { get; set; } + + public int Value { get; set; } + + public virtual Viking? Viking { get; set; } +} diff --git a/src/Model/DBContext.cs b/src/Model/DBContext.cs index 6636393..d2ef372 100644 --- a/src/Model/DBContext.cs +++ b/src/Model/DBContext.cs @@ -46,6 +46,9 @@ public class DBContext : DbContext { builder.Entity().HasMany(v => v.Rooms) .WithOne(e => e.Viking); + builder.Entity().HasMany(v => v.AchievementPoints) + .WithOne(e => e.Viking); + builder.Entity().HasOne(s => s.User) .WithMany(e => e.Vikings) .HasForeignKey(e => e.UserId); @@ -130,5 +133,12 @@ public class DBContext : DbContext { builder.Entity().HasOne(i => i.Room) .WithMany(r => r.Items) .HasForeignKey(e => e.RoomId); + + builder.Entity().HasKey(e => new { e.VikingId, e.Type }); + + builder.Entity() + .HasOne(e => e.Viking) + .WithMany(e => e.AchievementPoints) + .HasForeignKey(e => e.VikingId); } } diff --git a/src/Model/Dragon.cs b/src/Model/Dragon.cs index 1d80e8b..955da55 100644 --- a/src/Model/Dragon.cs +++ b/src/Model/Dragon.cs @@ -18,6 +18,8 @@ public class Dragon { public string? RaisedPetData { get; set; } + public int? PetXP { get; set; } + public virtual Viking Viking { get; set; } = null!; public virtual Viking SelectedViking { get; set; } = null!; } diff --git a/src/Model/Viking.cs b/src/Model/Viking.cs index fa3ba14..aed5ce4 100644 --- a/src/Model/Viking.cs +++ b/src/Model/Viking.cs @@ -21,6 +21,7 @@ public class Viking { public virtual ICollection Images { get; set; } = null!; public virtual ICollection MissionStates { get; set; } = null!; public virtual ICollection Rooms { get; set; } = null!; + public virtual ICollection AchievementPoints { get; set; } = null!; public virtual Dragon? SelectedDragon { get; set; } public int InventoryId { get; set; } diff --git a/src/Program.cs b/src/Program.cs index 65ea233..41ebc00 100644 --- a/src/Program.cs +++ b/src/Program.cs @@ -18,6 +18,7 @@ builder.Services.AddSingleton(); builder.Services.AddScoped(); builder.Services.AddSingleton(); builder.Services.AddScoped(); +builder.Services.AddSingleton(); var app = builder.Build(); diff --git a/src/Schema/AchievementPointTypes.cs b/src/Schema/AchievementPointTypes.cs new file mode 100644 index 0000000..d1e6c28 --- /dev/null +++ b/src/Schema/AchievementPointTypes.cs @@ -0,0 +1,35 @@ +using System.Xml.Serialization; + +namespace sodoff.Schema; + +public enum AchievementPointTypes { + [XmlEnum("1")] + PlayerXP = 1, + + [XmlEnum("2")] + GameCurrency = 2, // gold + + [XmlEnum("4")] + Unknown4 = 4, + + [XmlEnum("5")] + CashCurrency = 5, // gems + + [XmlEnum("6")] + ItemReward = 6, + + [XmlEnum("8")] + DragonXP = 8, + + [XmlEnum("9")] + PlayerFarmingXP = 9, + + [XmlEnum("10")] + PlayerFishingXP = 10, + + [XmlEnum("12")] + UDTPoints = 12, + + [XmlEnum("13")] + Unknown13 = 13, +} diff --git a/src/Schema/AchievementReward.cs b/src/Schema/AchievementReward.cs index 9ca16c1..2dc5b5e 100644 --- a/src/Schema/AchievementReward.cs +++ b/src/Schema/AchievementReward.cs @@ -13,7 +13,7 @@ public class AchievementReward public int? Amount; [XmlElement(ElementName = "p", IsNullable = true)] - public int? PointTypeID; + public AchievementPointTypes? PointTypeID; [XmlElement(ElementName = "ii")] public int ItemID; diff --git a/src/Schema/ArrayOfUserRank.cs b/src/Schema/ArrayOfUserRank.cs new file mode 100644 index 0000000..e1edb00 --- /dev/null +++ b/src/Schema/ArrayOfUserRank.cs @@ -0,0 +1,11 @@ +using System.Diagnostics; +using System.Xml.Serialization; + +namespace sodoff.Schema; + +[XmlRoot(ElementName = "ArrayOfUserRank", Namespace = "http://api.jumpstart.com/")] +[Serializable] +public class ArrayOfUserRank { + [XmlElement(ElementName = "UserRank")] + public UserRank[] UserRank; +} diff --git a/src/Schema/AvatarData.cs b/src/Schema/AvatarData.cs index 6b50534..ae55e3e 100644 --- a/src/Schema/AvatarData.cs +++ b/src/Schema/AvatarData.cs @@ -7,6 +7,7 @@ namespace sodoff.Schema; public class AvatarData { [XmlElement(ElementName = "IsSuggestedAvatarName", IsNullable = true)] + public bool? IsSuggestedAvatarName; public int? Id; diff --git a/src/Schema/ItemStateCriteriaReplenishable.cs b/src/Schema/ItemStateCriteriaReplenishable.cs index f4812fd..51aca38 100644 --- a/src/Schema/ItemStateCriteriaReplenishable.cs +++ b/src/Schema/ItemStateCriteriaReplenishable.cs @@ -10,7 +10,7 @@ public class ItemStateCriteriaReplenishable : ItemStateCriteria public bool ApplyRank; [XmlElement(ElementName = "PointTypeID", IsNullable = true)] - public int? PointTypeID; + public AchievementPointTypes? PointTypeID; [XmlElement(ElementName = "ReplenishableRates")] public List ReplenishableRates; diff --git a/src/Schema/RewardMultiplier.cs b/src/Schema/RewardMultiplier.cs index 53a23f9..ecb6a1d 100644 --- a/src/Schema/RewardMultiplier.cs +++ b/src/Schema/RewardMultiplier.cs @@ -7,7 +7,7 @@ namespace sodoff.Schema; public class RewardMultiplier { [XmlElement(ElementName = "PT")] - public int PointTypeID; + public AchievementPointTypes PointTypeID; [XmlElement(ElementName = "MF")] public int MultiplierFactor; diff --git a/src/Schema/UserAchievementInfo.cs b/src/Schema/UserAchievementInfo.cs index d8b2d75..1494e3a 100644 --- a/src/Schema/UserAchievementInfo.cs +++ b/src/Schema/UserAchievementInfo.cs @@ -19,7 +19,7 @@ public class UserAchievementInfo public int RankID; [XmlElement(ElementName = "p")] - public int? PointTypeID; + public AchievementPointTypes? PointTypeID; [XmlElement(ElementName = "FBUID", IsNullable = true)] public long? FacebookUserID; diff --git a/src/Schema/UserRank.cs b/src/Schema/UserRank.cs new file mode 100644 index 0000000..9063dd8 --- /dev/null +++ b/src/Schema/UserRank.cs @@ -0,0 +1,20 @@ +using System.Diagnostics; +using System.Xml.Serialization; + +namespace sodoff.Schema; + +[XmlRoot(ElementName = "UserRank", Namespace = "")] +[Serializable] +public class UserRank { + [XmlElement(ElementName = "PointTypeID")] + public AchievementPointTypes PointTypeID; + + [XmlElement(ElementName = "Value")] + public int Value; + + [XmlElement(ElementName = "RankID")] + public int? RankID; + + [XmlElement(ElementName = "GlobalRankID")] + public int? GlobalRankID; +} diff --git a/src/Services/AchievementService.cs b/src/Services/AchievementService.cs new file mode 100644 index 0000000..a6ea48b --- /dev/null +++ b/src/Services/AchievementService.cs @@ -0,0 +1,56 @@ +using sodoff.Schema; +using sodoff.Model; +using sodoff.Util; +using System.Reflection; +using System.Xml; +using System.Xml.Linq; + +namespace sodoff.Services { + public class AchievementService { + + Dictionary ranks = new(); + + public AchievementService() { + ArrayOfUserRank allranks = XmlUtil.DeserializeXml(XmlUtil.ReadResourceXmlString("allranks")); + + foreach (var pointType in Enum.GetValues()) { + ranks[pointType] = allranks.UserRank.Where(r => r.PointTypeID == pointType).ToArray(); + } + } + + public int GetRankFromXP(int? xpPoints, AchievementPointTypes type) { + return ranks[type].Count(r => r.Value <= xpPoints); + } + + public UserAchievementInfo CreateUserAchievementInfo(string userId, int? value, AchievementPointTypes type) { + if (value is null) + value = 0; + return new UserAchievementInfo { + UserID = Guid.Parse(userId), + AchievementPointTotal = value, + RankID = GetRankFromXP(value, type), + PointTypeID = type + }; + } + + public UserAchievementInfo CreateUserAchievementInfo(Viking viking, AchievementPointTypes type) { + return CreateUserAchievementInfo(viking.Id, viking.AchievementPoints.FirstOrDefault(a => a.Type == (int)type)?.Value, type); + } + + public void AddAchievementPoints(Viking viking, AchievementPointTypes? type, int? value) { + if (type == AchievementPointTypes.DragonXP) { + viking.SelectedDragon.PetXP = (viking.SelectedDragon.PetXP ?? 0) + (value ?? 0); + } else if (type != null) { + AchievementPoints xpPoints = viking.AchievementPoints.FirstOrDefault(a => a.Type == (int)type); + if (xpPoints is null) { + xpPoints = new AchievementPoints { + Type = (int)type, + Value = 0 + }; + viking.AchievementPoints.Add(xpPoints); + } + xpPoints.Value += value ?? 0; + } + } + } +} diff --git a/src/Services/MissionService.cs b/src/Services/MissionService.cs index 79f7fcc..8ad337c 100644 --- a/src/Services/MissionService.cs +++ b/src/Services/MissionService.cs @@ -8,8 +8,9 @@ public class MissionService { private readonly DBContext ctx; private MissionStoreSingleton missionStore; + private AchievementService achievementService; - public MissionService(DBContext ctx, MissionStoreSingleton missionStore) { + public MissionService(DBContext ctx, MissionStoreSingleton missionStore, AchievementService achievementService) { this.ctx = ctx; this.missionStore = missionStore; } @@ -49,7 +50,7 @@ public class MissionService { missionState.UserAccepted = null; } foreach (var reward in mission.Rewards) { - if (reward.PointTypeID == 6) { + if (reward.PointTypeID == AchievementPointTypes.ItemReward) { // TODO: This is not a pretty solution. Use inventory service in the future InventoryItem? ii = viking.Inventory.InventoryItems.FirstOrDefault(x => x.ItemId == reward.ItemID); if (ii is null) { @@ -60,6 +61,8 @@ public class MissionService { viking.Inventory.InventoryItems.Add(ii); } ii.Quantity += (int)reward.Amount!; + } else { // currencies, all types of player XP and dragon XP + achievementService.AddAchievementPoints(viking, reward.PointTypeID, reward.Amount); } } ctx.SaveChanges(); diff --git a/src/Services/RoomService.cs b/src/Services/RoomService.cs index cc1df72..291d7e5 100644 --- a/src/Services/RoomService.cs +++ b/src/Services/RoomService.cs @@ -125,7 +125,7 @@ public class RoomService { if (rewards != null) { response.Rewards = rewards; foreach (var reward in rewards) { - if (reward.PointTypeID == 6) { + if (reward.PointTypeID == AchievementPointTypes.ItemReward) { // TODO: This is not a pretty solution. Use inventory service in the future InventoryItem? ii = item.Room.Viking.Inventory.InventoryItems.FirstOrDefault(x => x.ItemId == reward.ItemID); if (ii is null) {