diff --git a/src/Controllers/Common/ContentController.cs b/src/Controllers/Common/ContentController.cs index 1c2e6eb..8935881 100644 --- a/src/Controllers/Common/ContentController.cs +++ b/src/Controllers/Common/ContentController.cs @@ -1185,6 +1185,42 @@ public class ContentController : Controller { return Ok(new BuddyList { Buddy = new Buddy[0] }); } + [HttpPost] + [Produces("application/xml")] + [Route("/ContentWebService.asmx/RedeemItems")] + [VikingSession] + public IActionResult RedeemItems(Viking viking, [FromForm] string request) { + var req = XmlUtil.DeserializeXml(request); + Dictionary inventoryItemsToAdd = new(); + + // resolve items in the bundle + ItemData bundleItem = itemService.GetItem(req.ItemID); + foreach (var reward in bundleItem.Relationship.Where(e => e.Type == "Bundle")) { + int quantity = itemService.GetItemQuantity(reward, 1); + inventoryItemsToAdd.TryAdd(reward.ItemId, 0); + inventoryItemsToAdd[reward.ItemId] += quantity; + } + + var addedItems = inventoryService.AddItemsToInventoryBulk(viking, inventoryItemsToAdd); + + // build response + List items = new List(); + foreach (var i in inventoryItemsToAdd) { + items.AddRange(Enumerable.Repeat( + new CommonInventoryResponseItem { + CommonInventoryID = addedItems.ContainsKey(i.Key) ? addedItems[i.Key] : 0, // return inventory id if this item was added to the DB + ItemID = i.Key, + Quantity = 0 + }, i.Value)); + } + + return Ok(new CommonInventoryResponse{ + Success = true, + CommonInventoryIDs = items.ToArray(), + UserGameCurrency = achievementService.GetUserCurrency(viking) + }); + } + [HttpPost] [Produces("application/xml")] [Route("/ContentWebService.asmx/RedeemMysteryBoxItems")] diff --git a/src/Controllers/Common/RegistrationController.cs b/src/Controllers/Common/RegistrationController.cs index 977da30..708dad1 100644 --- a/src/Controllers/Common/RegistrationController.cs +++ b/src/Controllers/Common/RegistrationController.cs @@ -100,7 +100,6 @@ public class RegistrationController : Controller { [HttpPost] [Produces("application/xml")] - [Route("V3/RegistrationWebService.asmx/RegisterChild")] // used by Magic & Mythies [Route("V4/RegistrationWebService.asmx/RegisterChild")] [DecryptRequest("childRegistrationData")] [EncryptResponse] @@ -131,6 +130,34 @@ public class RegistrationController : Controller { }); } + [HttpPost] + [Produces("application/xml")] + [Route("V3/RegistrationWebService.asmx/RegisterChild")] // used by SoD 1.13 and Magic & Mythies + [DecryptRequest("childRegistrationData")] + public IActionResult RegisterChildV3([FromForm] Guid parentApiToken, [FromForm] string apiKey) { + User? user = ctx.Sessions.FirstOrDefault(e => e.ApiToken == parentApiToken)?.User; + if (user is null) { + return Ok(new RegistrationResult{ + Status = MembershipUserStatus.InvalidApiToken + }); + } + + // Check if name populated + ChildRegistrationData data = XmlUtil.DeserializeXml(Request.Form["childRegistrationData"]); + if (String.IsNullOrWhiteSpace(data.ChildName)) { + return Ok(MembershipUserStatus.ValidationError); + } + + // Check if viking exists + if (ctx.Vikings.Count(e => e.Name == data.ChildName) > 0) { + return Ok(MembershipUserStatus.DuplicateUserName); + } + + Viking v = CreateViking(user, data, ClientVersion.GetVersion(apiKey)); + + return Ok(MembershipUserStatus.Success); + } + private Viking CreateViking(User user, ChildRegistrationData data, uint gameVersion) { List items = new(); if (gameVersion >= ClientVersion.Min_SoD) { diff --git a/src/Resources/achievements/achievementid_sod.xml b/src/Resources/achievements/achievementid_sod.xml index 6ca3194..d95b24f 100644 --- a/src/Resources/achievements/achievementid_sod.xml +++ b/src/Resources/achievements/achievementid_sod.xml @@ -3029,6 +3029,164 @@ + + + 201388 + +

6

+ 3 + 1 + 8 + 7143 +
+ +

6

+ 2 + 1 + 8 + 7139 +
+ +

6

+ 1 + 1 + 8 + 7144 +
+
+ + 201389 + +

6

+ 4 + 1 + 8 + 7143 +
+
+ + 201390 + +

6

+ 2 + 1 + 8 + 7143 +
+ +

6

+ 1 + 1 + 8 + 7139 +
+
+ + 201391 + +

6

+ 3 + 1 + 8 + 7140 +
+
+ + 201392 + +

6

+ 1 + 1 + 8 + 7140 +
+ +

6

+ 3 + 1 + 8 + 7144 +
+
+ + 201393 + +

6

+ 5 + 1 + 8 + 7143 +
+ +

6

+ 1 + 1 + 8 + 7140 +
+
+ + 201394 + +

6

+ 1 + 1 + 8 + 7143 +
+ +

6

+ 2 + 1 + 8 + 7139 +
+ +

6

+ 2 + 1 + 8 + 7144 +
+
+ + 201395 + +

6

+ 5 + 1 + 8 + 7139 +
+
+ + 201396 + +

6

+ 5 + 1 + 8 + 7144 +
+
+ + 201397 + +

6

+ 3 + 1 + 8 + 7139 +
+ +

6

+ 2 + 1 + 8 + 7144 +
+
+ 201323 diff --git a/src/Schema/AchievementCompletion.cs b/src/Schema/AchievementCompletion.cs new file mode 100644 index 0000000..8182bbd --- /dev/null +++ b/src/Schema/AchievementCompletion.cs @@ -0,0 +1,11 @@ +using System.Xml.Serialization; + +namespace sodoff.Schema; + +[XmlRoot(ElementName = "AchievementCompletion", Namespace = "")] +[Serializable] +public class AchievementCompletion +{ + [XmlElement(ElementName = "AchievementID")] + public int AchievementID; +} diff --git a/src/Schema/CompletionAction.cs b/src/Schema/CompletionAction.cs index 5d49da3..98cd35a 100644 --- a/src/Schema/CompletionAction.cs +++ b/src/Schema/CompletionAction.cs @@ -12,4 +12,7 @@ public class CompletionAction { [XmlElement(ElementName = "Transition")] public StateTransition Transition; + + [XmlElement(ElementName = "AchievementCompletion", IsNullable = true)] + public AchievementCompletion[] AchievementCompletion; } diff --git a/src/Services/RoomService.cs b/src/Services/RoomService.cs index c16c342..a5d30b8 100644 --- a/src/Services/RoomService.cs +++ b/src/Services/RoomService.cs @@ -12,6 +12,7 @@ public class RoomService { private ItemService itemService; private AchievementService achievementService; + private Random random = new Random(); public RoomService(DBContext ctx, ItemService itemService, AchievementService achievementService) { this.ctx = ctx; @@ -125,8 +126,9 @@ public class RoomService { UserItemPosition pos = XmlUtil.DeserializeXml(item.RoomItemData); AchievementReward[]? rewards; + int? achievementID; List consumables; - int nextStateID = GetNextStateID(pos, speedup, out rewards, out consumables); + int nextStateID = GetNextStateID(pos, speedup, out rewards, out achievementID, out consumables); foreach (var consumable in consumables) { ItemStateCriteriaConsumable c = (ItemStateCriteriaConsumable)consumable; @@ -138,6 +140,13 @@ public class RoomService { if (rewards != null) { response.Rewards = achievementService.ApplyAchievementRewards(item.Room.Viking, rewards); } + if (achievementID != null) { + var newrewards = achievementService.ApplyAchievementRewardsByID(item.Room.Viking, (int)achievementID); + if (response.Rewards is null) + response.Rewards = newrewards; + else + response.Rewards = response.Rewards.Concat(newrewards).ToArray(); + } DateTime stateChange = new DateTime(DateTime.Now.Ticks); if (nextStateID == -1) { @@ -164,8 +173,9 @@ public class RoomService { return response; } - private int GetNextStateID(UserItemPosition pos, bool speedup, out AchievementReward[]? rewards, out List consumables) { + private int GetNextStateID(UserItemPosition pos, bool speedup, out AchievementReward[]? rewards, out int? achievementID, out List consumables) { rewards = null; + achievementID = null; consumables = new List(); if (pos.UserItemState == null) @@ -175,6 +185,13 @@ public class RoomService { rewards = currState.Rewards; consumables = currState.Rule.Criterias.FindAll(x => x.Type == ItemStateCriteriaType.ConsumableItem); + // achievementID = currState.AchievementID; // TODO we should do this or not? some items use the same rewards in `currState.Rewards` and achievement definition, but some do not contain only achievementID (but then there is generally no definition for achievement) + if (currState.Rule.CompletionAction.AchievementCompletion != null) { + achievementID = currState.Rule.CompletionAction.AchievementCompletion[ + random.Next(0, currState.Rule.CompletionAction.AchievementCompletion.Length) + ].AchievementID; + } + if (speedup) return ((ItemStateCriteriaSpeedUpItem)currState.Rule.Criterias.Find(x => x.Type == ItemStateCriteriaType.SpeedUpItem)!).EndStateID;