diff --git a/README.md b/README.md index 4f466c0..4998895 100644 --- a/README.md +++ b/README.md @@ -79,6 +79,9 @@ Then run School of Dragons. - SetAchievementAndGetReward - GetAchievementsByUserID - GetPetAchievementsByUserID +- RerollUserItem +- FuseItems +- AddBattleItems #### Implemented enough (probably) - GetRules (doesn't return any rules, probably doesn't need to) @@ -101,6 +104,8 @@ Then run School of Dragons. - SetNextItemState (may require more work, we don't know yet) - SetUserRoom - SetUserAchievementTask (returns a real reward but still use task placeholder) +- SellItems (need add cash reward) +- ApplyRewards #### Currently static or stubbed - GetAllRanks (needs to be populated with what ranks the user has) diff --git a/mitm-redirect.py b/mitm-redirect.py index ec8ca25..5b4bdcb 100644 --- a/mitm-redirect.py +++ b/mitm-redirect.py @@ -70,6 +70,11 @@ methods = [ 'GetUserGameCurrency', 'SetAchievementByEntityIDs', 'UseInventory', + 'SellItems', + 'FuseItems', + 'RerollUserItem', + 'AddBattleItems', + 'ApplyRewards', 'DeleteProfile', 'DeleteAccountNotification', 'SetUserAchievementAndGetReward', diff --git a/src/Controllers/Common/ContentController.cs b/src/Controllers/Common/ContentController.cs index 2379e8f..9f67a4a 100644 --- a/src/Controllers/Common/ContentController.cs +++ b/src/Controllers/Common/ContentController.cs @@ -16,13 +16,16 @@ public class ContentController : Controller { private MissionService missionService; private RoomService roomService; private AchievementService achievementService; - public ContentController(DBContext ctx, KeyValueService keyValueService, ItemService itemService, MissionService missionService, RoomService roomService, AchievementService achievementService) { + private InventoryService inventoryService; + private Random random = new Random(); + public ContentController(DBContext ctx, KeyValueService keyValueService, ItemService itemService, MissionService missionService, RoomService roomService, AchievementService achievementService, InventoryService inventoryService) { this.ctx = ctx; this.keyValueService = keyValueService; this.itemService = itemService; this.missionService = missionService; this.roomService = roomService; this.achievementService = achievementService; + this.inventoryService = inventoryService; } [HttpPost] @@ -178,40 +181,25 @@ public class ContentController : Controller { return Ok(); } - - List userItemData = new(); + List userItemData; if (viking != null) { - List items = viking.Inventory.InventoryItems.ToList(); - foreach (InventoryItem item in items) { - if (item.Quantity == 0) continue; // Don't include an item that the viking doesn't have - ItemData itemData = itemService.GetItem(item.ItemId); - UserItemData uid = new UserItemData { - UserInventoryID = item.Id, - ItemID = itemData.ItemID, - Quantity = item.Quantity, - Uses = itemData.Uses, - ModifiedDate = new DateTime(DateTime.Now.Ticks), - Item = itemData - }; - userItemData.Add(uid); - } + return Ok( inventoryService.GetCommonInventoryData(viking) ); } else { // TODO: placeholder - return 8 viking slot items - UserItemData uid = new UserItemData { - UserInventoryID = 0, - ItemID = 7971, - Quantity = 8, - Uses = -1, - ModifiedDate = new DateTime(DateTime.Now.Ticks), - Item = itemService.GetItem(7971) - }; - userItemData.Add(uid); + return Ok(new CommonInventoryData { + UserID = Guid.Parse(user.Id), + Item = new UserItemData[] { + new UserItemData { + UserInventoryID = 0, + ItemID = 7971, + Quantity = 8, + Uses = -1, + ModifiedDate = new DateTime(DateTime.Now.Ticks), + Item = itemService.GetItem(7971) + } + } + }); } - - return Ok(new CommonInventoryData { - UserID = Guid.Parse(user is not null ? user.Id : viking.Id), - Item = userItemData.ToArray() - }); } [HttpPost] @@ -221,27 +209,7 @@ public class ContentController : Controller { Viking? viking = ctx.Sessions.FirstOrDefault(e => e.ApiToken == apiToken)?.Viking; if (viking is null || viking.Inventory is null) return Ok(); - List items = viking.Inventory.InventoryItems.ToList(); - List userItemData = new(); - foreach (InventoryItem item in items) { - if (item.Quantity == 0) continue; // Don't include an item that the viking doesn't have - ItemData itemData = itemService.GetItem(item.ItemId); - UserItemData uid = new UserItemData { - UserInventoryID = item.Id, - ItemID = itemData.ItemID, - Quantity = item.Quantity, - Uses = itemData.Uses, - ModifiedDate = new DateTime(DateTime.Now.Ticks), - Item = itemData - }; - userItemData.Add(uid); - } - - CommonInventoryData invData = new CommonInventoryData { - UserID = Guid.Parse(viking.UserId), - Item = userItemData.ToArray() - }; - return Ok(invData); + return Ok(inventoryService.GetCommonInventoryData(viking)); } [HttpPost] @@ -267,7 +235,7 @@ public class ContentController : Controller { foreach (var req in request) { if (req.ItemID == 0) continue; // Do not save a null item - if (IsFarmExpansion((int)req.ItemID)) { + if (inventoryService.ItemNeedUniqueInventorySlot((int)req.ItemID)) { // if req.Quantity < 0 remove unique items for (int i=req.Quantity; i<0; ++i) { InventoryItem? item = viking.Inventory.InventoryItems.FirstOrDefault(e => e.ItemId == req.ItemID && e.Quantity>0); @@ -275,32 +243,16 @@ public class ContentController : Controller { } // if req.Quantity > 0 add unique items for (int i=0; i e.ItemId == req.ItemID); - if (item is null) { - item = new InventoryItem { ItemId = (int)req.ItemID, Quantity = 0 }; - viking.Inventory.InventoryItems.Add(item); + if (req.Quantity > 0) { + responseItems.Add( + inventoryService.AddItemToInventoryAndGetResponse(viking, (int)req.ItemID!, req.Quantity) + ); } - int updateQuantity = 0; // The game expects 0 if quantity got updated by just 1 - if (req.Quantity > 1) - updateQuantity = req.Quantity; // Otherwise it expects the quantity from the request - item.Quantity += req.Quantity; - ctx.SaveChanges(); // We need to get the ID of the newly created item - if (req.Quantity > 0) - responseItems.Add(new CommonInventoryResponseItem { - CommonInventoryID = item.Id, - ItemID = item.ItemId, - Quantity = updateQuantity - }); } } @@ -771,20 +723,9 @@ public class ContentController : Controller { PurchaseStoreItemRequest request = XmlUtil.DeserializeXml(purchaseItemRequest); CommonInventoryResponseItem[] items = new CommonInventoryResponseItem[request.Items.Length]; for (int i = 0; i < request.Items.Length; i++) { - InventoryItem? item = null; - if (!IsFarmExpansion(request.Items[i])) - item = viking.Inventory.InventoryItems.FirstOrDefault(e => e.ItemId == request.Items[i]); - if (item is null) { - item = new InventoryItem { ItemId = request.Items[i], Quantity = 0 }; - viking.Inventory.InventoryItems.Add(item); - } - item.Quantity++; - ctx.SaveChanges(); - items[i] = new CommonInventoryResponseItem { - CommonInventoryID = item.Id, - ItemID = request.Items[i], - Quantity = 0 // The quantity of purchased items is always 0 and the items are instead duplicated in both the request and the response - }; + items[i] = inventoryService.AddItemToInventoryAndGetResponse(viking, request.Items[i], 1); + // NOTE: The quantity of purchased items is always 0 and the items are instead duplicated in both the request and the response. + // Call AddItemToInventoryAndGetResponse with Quantity == 1 we get response with quantity == 0. } CommonInventoryResponse response = new CommonInventoryResponse { @@ -811,18 +752,9 @@ public class ContentController : Controller { int[] itemIdArr = XmlUtil.DeserializeXml(itemIDArrayXml); CommonInventoryResponseItem[] items = new CommonInventoryResponseItem[itemIdArr.Length]; for (int i = 0; i < itemIdArr.Length; i++) { - InventoryItem? item = viking.Inventory.InventoryItems.FirstOrDefault(e => e.ItemId == itemIdArr[i]); - if (item is null) { - item = new InventoryItem { ItemId = itemIdArr[i], Quantity = 0 }; - viking.Inventory.InventoryItems.Add(item); - } - item.Quantity++; - ctx.SaveChanges(); - items[i] = new CommonInventoryResponseItem { - CommonInventoryID = item.Id, - ItemID = itemIdArr[i], - Quantity = 0 // The quantity of purchased items is always 0 and the items are instead duplicated in both the request and the response - }; + items[i] = inventoryService.AddItemToInventoryAndGetResponse(viking, itemIdArr[i], 1); + // NOTE: The quantity of purchased items is always 0 and the items are instead duplicated in both the request and the response. + // Call AddItemToInventoryAndGetResponse with Quantity == 1 we get response with quantity == 0. } CommonInventoryResponse response = new CommonInventoryResponse { @@ -991,6 +923,310 @@ public class ContentController : Controller { }); } + [HttpPost] + [Produces("application/xml")] + [Route("V2/ContentWebService.asmx/RerollUserItem")] + public IActionResult RerollUserItem([FromForm] string apiToken, [FromForm] string request) { + Viking? viking = ctx.Sessions.FirstOrDefault(e => e.ApiToken == apiToken)?.Viking; + if (viking is null || viking.Inventory is null) return Unauthorized(); + + RollUserItemRequest req = XmlUtil.DeserializeXml(request); + + // get item + InventoryItem? invItem = viking.Inventory.InventoryItems.FirstOrDefault(e => e.Id == req.UserInventoryID); + if (invItem is null) + return Ok(new RollUserItemResponse { Status = Status.ItemNotFound }); + + // get item data and stats + ItemData itemData = itemService.GetItem(invItem.ItemId); + ItemStatsMap itemStatsMap; + if (invItem.StatsSerialized != null) { + itemStatsMap = XmlUtil.DeserializeXml(invItem.StatsSerialized); + } else { + itemStatsMap = itemData.ItemStatsMap; + } + + List newStats; + Status status = Status.Failure; + + // update stats + if (req.ItemStatNames != null) { + // reroll only one stat (from req.ItemStatNames) + newStats = new List(); + foreach (string name in req.ItemStatNames) { + ItemStat itemStat = itemStatsMap.ItemStats.FirstOrDefault(e => e.Name == name); + + // draw new stats + StatRangeMap rangeMap = itemData.PossibleStatsMap.Stats.FirstOrDefault(e => e.ItemStatsID == itemStat.ItemStatID).ItemStatsRangeMaps.FirstOrDefault(e => e.ItemTierID == (int)(itemStatsMap.ItemTier)); + int newVal = random.Next(rangeMap.StartRange, rangeMap.EndRange+1); + + // check draw results + Int32.TryParse(itemStat.Value, out int oldVal); + if (newVal > oldVal) { + itemStat.Value = newVal.ToString(); + newStats.Add(itemStat); + status = Status.Success; + } + } + // get shards + inventoryService.AddItemToInventory(viking, InventoryService.Shards, -((int)(itemData.ItemRarity) + (int)(itemStatsMap.ItemTier) - 1)); + } else { + // reroll full item + newStats = itemService.CreateItemStats(itemData.PossibleStatsMap, (int)itemData.ItemRarity, (int)itemStatsMap.ItemTier); + itemStatsMap.ItemStats = newStats.ToArray(); + status = Status.Success; + // get shards + int price = 0; + switch (itemData.ItemRarity) { + case ItemRarity.Common: + price = 5; + break; + case ItemRarity.Rare: + price = 7; + break; + case ItemRarity.Epic: + price = 10; + break; + case ItemRarity.Legendary: + price = 20; + break; + } + switch (itemStatsMap.ItemTier) { + case ItemTier.Tier2: + price = (int)Math.Floor(price * 1.5); + break; + case ItemTier.Tier3: + case ItemTier.Tier4: + price = price * 2; + break; + } + inventoryService.AddItemToInventory(viking, InventoryService.Shards, -price); + } + + // save + invItem.StatsSerialized = XmlUtil.SerializeXml(itemStatsMap); + ctx.SaveChanges(); + + // return results + return Ok(new RollUserItemResponse { + Status = status, + ItemStats = newStats.ToArray() // we need return only updated stats, so can't `= itemStatsMap.ItemStats` + }); + } + + [HttpPost] + [Produces("application/xml")] + [Route("V2/ContentWebService.asmx/FuseItems")] + public IActionResult FuseItems([FromForm] string apiToken, [FromForm] string fuseItemsRequest) { + Viking? viking = ctx.Sessions.FirstOrDefault(e => e.ApiToken == apiToken)?.Viking; + if (viking is null || viking.Inventory is null) return Unauthorized(); + + FuseItemsRequest req = XmlUtil.DeserializeXml(fuseItemsRequest); + + ItemData blueprintItem; + try { + blueprintItem = itemService.GetItem(req.BluePrintItemID ?? -1); + } catch(System.Collections.Generic.KeyNotFoundException) { + return Ok(new FuseItemsResponse { Status = Status.BluePrintItemNotFound }); + } + + // TODO: check for blueprintItem.BluePrint.Deductibles and blueprintItem.BluePrint.Ingredients + + // remove items from DeductibleItemInventoryMaps and BluePrintFuseItemMaps + foreach (var item in req.DeductibleItemInventoryMaps) { + InventoryItem? invItem = viking.Inventory.InventoryItems.FirstOrDefault(e => e.Id == item.UserInventoryID); + invItem.Quantity -= item.Quantity; + } + foreach (var item in req.BluePrintFuseItemMaps) { + InventoryItem? invItem = viking.Inventory.InventoryItems.FirstOrDefault(e => e.Id == item.UserInventoryID); + viking.Inventory.InventoryItems.Remove(invItem); + } + + var resItemList = new List(); + foreach (BluePrintSpecification output in blueprintItem.BluePrint.Outputs) { + if (output.ItemID is null) + continue; + + // get new item info + int newItemId = (int)(output.ItemID); + ItemData newItemData = itemService.GetItem(newItemId); + + // check for "box tickets" + if (itemService.ItemHasCategory(newItemData, 462)) { + newItemId = itemService.OpenBox(newItemData).ItemId; + } + + resItemList.Add( + inventoryService.AddBattleItemToInventory(viking, newItemId, (int)output.Tier) + ); + } + + // return response with new item info + return Ok(new FuseItemsResponse { + Status = Status.Success, + InventoryItemStatsMaps = resItemList + }); + } + + [HttpPost] + [Produces("application/xml")] + [Route("V2/ContentWebService.asmx/SellItems")] + public IActionResult SellItems([FromForm] string apiToken, [FromForm] string sellItemsRequest) { + Viking? viking = ctx.Sessions.FirstOrDefault(e => e.ApiToken == apiToken)?.Viking; + if (viking is null || viking.Inventory is null) return Unauthorized(); + + int price = 0; + SellItemsRequest req = XmlUtil.DeserializeXml(sellItemsRequest); + foreach (var invItemID in req.UserInventoryCommonIDs) { + // get item from inventory + InventoryItem? item = viking.Inventory.InventoryItems.FirstOrDefault(e => e.Id == invItemID); + + // get item data + ItemData? itemData = itemService.GetItem(item.ItemId); + + // calculate price + switch (itemData.ItemRarity) { + case ItemRarity.Common: + price += 1; + break; + case ItemRarity.Rare: + price += 3; + break; + case ItemRarity.Epic: + price += 5; + break; + case ItemRarity.Legendary: + price += 10; + break; + } + + // TODO: cash rewards + + // remove item + viking.Inventory.InventoryItems.Remove(item); + } + + // apply shards reward + CommonInventoryResponseItem? resShardsItem = inventoryService.AddItemToInventoryAndGetResponse(viking, InventoryService.Shards, price); + + // save + ctx.SaveChanges(); + + // return success with shards reward + return Ok(new CommonInventoryResponse { + Success = true, + CommonInventoryIDs = new CommonInventoryResponseItem[] { + resShardsItem + } + }); + } + + [HttpPost] + [Produces("application/xml")] + [Route("V2/ContentWebService.asmx/AddBattleItems")] + public IActionResult AddBattleItems([FromForm] string apiToken, [FromForm] string request) { + Viking? viking = ctx.Sessions.FirstOrDefault(e => e.ApiToken == apiToken)?.Viking; + if (viking is null || viking.Inventory is null) return Unauthorized(); + + AddBattleItemsRequest req = XmlUtil.DeserializeXml(request); + + var resItemList = new List(); + foreach (BattleItemTierMap battleItemTierMap in req.BattleItemTierMaps) { + for (int i=0; i e.ApiToken == apiToken)?.Viking; + if (viking is null || viking.Inventory is null) return Unauthorized(); + + ApplyRewardsRequest req = XmlUtil.DeserializeXml(request); + + List achievementRewards = new List(); + UserItemStatsMap rewardedItem = null; + CommonInventoryResponse rewardedBlueprint = null; + + int rewardMultipler = 0; + if (req.LevelRewardType == LevelRewardType.LevelFailure) { + rewardMultipler = 1; + } else if (req.LevelRewardType == LevelRewardType.LevelCompletion) { + rewardMultipler = 2 * req.LevelDifficultyID; + } + + if (rewardMultipler > 0) { + // TODO: XP values and method of calculation is not grounded in anything ... + + // dragons XP + int dragonXp = 40 * rewardMultipler; + foreach (RaisedPetEntityMap petInfo in req.RaisedPetEntityMaps) { + Dragon? dragon = viking.Dragons.FirstOrDefault(e => e.Id == petInfo.RaisedPetID); + dragon.PetXP = (dragon.PetXP ?? 0) + dragonXp; + achievementRewards.Add(new AchievementReward{ + EntityID = petInfo.EntityID, + PointTypeID = AchievementPointTypes.DragonXP, + EntityTypeID = 3, // dragon ? + RewardID = 1265, // TODO: placeholder + Amount = dragonXp + }); + } + + // player XP and gems + achievementRewards.Add( + achievementService.AddAchievementPointsAndGetReward(viking, AchievementPointTypes.PlayerXP, 60 * rewardMultipler) + ); + achievementRewards.Add( + achievementService.AddAchievementPointsAndGetReward(viking, AchievementPointTypes.CashCurrency, 2 * rewardMultipler) + ); + } + + // - battle backpack items and blueprints + if (req.LevelRewardType != LevelRewardType.LevelFailure) { + ItemData rewardItem = itemService.GetDTReward(); + if (itemService.ItemHasCategory(rewardItem, 651)) { + // blueprint + CommonInventoryResponseItem blueprintItem = inventoryService.AddItemToInventoryAndGetResponse(viking, rewardItem.ItemID, 1); + rewardedBlueprint = new CommonInventoryResponse { + Success = true, + CommonInventoryIDs = new CommonInventoryResponseItem[] { + blueprintItem + } + }; + } else { + // DT item + InventoryItemStatsMap item = inventoryService.AddBattleItemToInventory(viking, rewardItem.ItemID, random.Next(1, 4)); + rewardedItem = new UserItemStatsMap { + Item = item.Item, + ItemStats = item.ItemStatsMap.ItemStats, + ItemTier = item.ItemStatsMap.ItemTier, + CreatedDate = new DateTime(DateTime.Now.Ticks) + }; + } + } + + // save + ctx.SaveChanges(); + + return Ok(new ApplyRewardsResponse { + Status = Status.Success, + AchievementRewards = achievementRewards.ToArray(), + RewardedItemStatsMap = rewardedItem, + CommonInventoryResponse = rewardedBlueprint, + }); + } + private RaisedPetData GetRaisedPetDataFromDragon (Dragon dragon) { RaisedPetData data = XmlUtil.DeserializeXml(dragon.RaisedPetData); data.RaisedPetID = dragon.Id; @@ -1062,18 +1298,6 @@ public class ContentController : Controller { }; } - private bool IsFarmExpansion(int itemId) { - ItemData? itemData = itemService.GetItem(itemId); - if (itemData != null && itemData.Category != null) { - foreach (ItemDataCategory itemCategory in itemData.Category) { - if (itemCategory.CategoryId == 541) { // if item is farm expansion - return true; - } - } - } - return false; - } - private void AddSuggestion(Random rand, string name, List suggestions) { if (ctx.Vikings.Any(x => x.Name == name) || suggestions.Contains(name)) { name += rand.Next(1, 5000); diff --git a/src/Model/InventoryItem.cs b/src/Model/InventoryItem.cs index ae150d5..e93d7f0 100644 --- a/src/Model/InventoryItem.cs +++ b/src/Model/InventoryItem.cs @@ -8,6 +8,8 @@ namespace sodoff.Model { public int ItemId { get; set; } public int InventoryId { get; set; } + + public string? StatsSerialized { get; set; } public virtual Inventory Inventory { get; set; } diff --git a/src/Program.cs b/src/Program.cs index 41ebc00..b86107e 100644 --- a/src/Program.cs +++ b/src/Program.cs @@ -19,6 +19,7 @@ builder.Services.AddScoped(); builder.Services.AddSingleton(); builder.Services.AddScoped(); builder.Services.AddSingleton(); +builder.Services.AddScoped(); var app = builder.Build(); diff --git a/src/Resources/achievementstasks.xml b/src/Resources/achievementstasks.xml index 73d0886..f65f615 100644 --- a/src/Resources/achievementstasks.xml +++ b/src/Resources/achievementstasks.xml @@ -517,6 +517,8 @@ 2261 + + 2262 + + 2263 @@ -610,6 +614,7 @@ 2264 + 100

1

@@ -658,7 +663,9 @@
2267 + + 2268 + + + + + 2269 + 2275 diff --git a/src/Resources/dtrewards.xml b/src/Resources/dtrewards.xml new file mode 100644 index 0000000..6075fc4 --- /dev/null +++ b/src/Resources/dtrewards.xml @@ -0,0 +1,309 @@ + + + 12374 + 12375 + 12377 + 12378 + 12379 + 12380 + 12381 + 12382 + 12383 + 12392 + 12393 + 12394 + 12396 + 12397 + 12398 + 13660 + 13661 + 13684 + 13685 + 13686 + 13687 + 13700 + 13701 + 13718 + 13719 + 13720 + 13721 + 13722 + 13723 + 13724 + 13725 + 13726 + 13727 + 13728 + 13747 + 13748 + 13749 + 13750 + 13751 + 13752 + 13753 + 13754 + 13755 + 13757 + 13758 + 13759 + 13760 + 13761 + 13762 + 13763 + 13764 + 13765 + 13766 + 13767 + 13768 + 13769 + 13770 + 13771 + 13772 + 13773 + 14842 + 14843 + 14844 + 14845 + 14846 + 14847 + 14848 + 14849 + 14850 + 14851 + 14852 + 14853 + 14854 + 14855 + 14856 + 14857 + 14858 + 14859 + 14860 + 14861 + 14862 + 14863 + 14864 + 14865 + 14866 + 14867 + 14868 + 14869 + 14870 + 14871 + 14873 + 14874 + 14875 + 14876 + 14877 + 14878 + 14879 + 14880 + 14881 + 14882 + 14883 + 14908 + 14921 + 14923 + 14933 + 15778 + 15781 + 15784 + 15785 + 15899 + 16249 + 16669 + 16670 + 16671 + 16729 + 16730 + 16731 + 16735 + 16806 + 17864 + 17865 + 17885 + 17886 + 17943 + 17997 + 18064 + 18099 + 18111 + 18274 + 18276 + 18278 + 18289 + 18290 + 18291 + 18339 + 18340 + 18467 + 18468 + 18482 + 18483 + 18489 + 18490 + 18491 + 18492 + 18493 + 18494 + 18495 + 18496 + 18497 + 18498 + 18501 + 18751 + 18752 + 18779 + 18780 + 18781 + 18782 + 18783 + 18784 + 18785 + 18812 + 18813 + 18814 + 19002 + 19021 + 19022 + 19023 + 19024 + 19025 + 19026 + 19027 + 19028 + 19029 + 19030 + 19046 + 19047 + 19112 + 19113 + 19181 + 20261 + 20262 + 20263 + 20304 + 20476 + 20477 + 20478 + 20492 + 20493 + 20496 + 20497 + 20498 + 20499 + 20500 + 20501 + 20502 + 20503 + 20504 + 20505 + 20506 + 20507 + 20508 + 20509 + 20510 + 20511 + 20512 + 20513 + 20515 + 20516 + 20517 + 20518 + 20519 + 20520 + 20521 + 20522 + 20523 + 20524 + 20525 + 20526 + 20527 + 20528 + 20529 + 20530 + 20531 + 20532 + 20533 + 20534 + 20535 + 20536 + 20537 + 20538 + 20539 + 20540 + 20541 + 20542 + 20543 + 20544 + 20545 + 20546 + 20547 + 20548 + 20549 + 20550 + 20551 + 20552 + 20553 + 20554 + 20555 + 20556 + 20557 + 20558 + 20559 + 20560 + 20561 + 20562 + 20563 + 20564 + 20565 + 20566 + 20567 + 20568 + 20569 + 20570 + 20571 + 20572 + 20573 + 20574 + 20575 + 20576 + 20577 + 20578 + 20579 + 20580 + 20581 + 20582 + 20583 + 20584 + 20585 + 20586 + 20587 + 20588 + 20589 + 20590 + 20591 + 20592 + 20593 + 20594 + 20595 + 20596 + 20597 + 20598 + 20599 + 20600 + 20601 + 20602 + 20603 + 20604 + 20605 + 20606 + 20607 + 20608 + 20609 + 20610 + 20857 + 20858 + 7149 + 7150 + diff --git a/src/Schema/AddBattleItemsRequest.cs b/src/Schema/AddBattleItemsRequest.cs new file mode 100644 index 0000000..649b96e --- /dev/null +++ b/src/Schema/AddBattleItemsRequest.cs @@ -0,0 +1,10 @@ +using System.Xml.Serialization; + +namespace sodoff.Schema; + +[XmlRoot(ElementName = "ABIR", Namespace = "")] +[Serializable] +public class AddBattleItemsRequest { + [XmlElement(ElementName = "BITM", IsNullable = false)] + public List BattleItemTierMaps { get; set; } +} diff --git a/src/Schema/AddBattleItemsResponse.cs b/src/Schema/AddBattleItemsResponse.cs new file mode 100644 index 0000000..8d06c37 --- /dev/null +++ b/src/Schema/AddBattleItemsResponse.cs @@ -0,0 +1,13 @@ +using System.Xml.Serialization; + +namespace sodoff.Schema; + +[XmlRoot(ElementName = "ABIRES", Namespace = "")] +[Serializable] +public class AddBattleItemsResponse { + [XmlElement(ElementName = "ST", IsNullable = false)] + public Status Status { get; set; } + + [XmlElement(ElementName = "IISM", IsNullable = true)] + public List InventoryItemStatsMaps { get; set; } +} diff --git a/src/Schema/ApplyRewardsRequest.cs b/src/Schema/ApplyRewardsRequest.cs new file mode 100644 index 0000000..9c91c1b --- /dev/null +++ b/src/Schema/ApplyRewardsRequest.cs @@ -0,0 +1,28 @@ +using System.Xml.Serialization; + +namespace sodoff.Schema; + +[XmlRoot(ElementName = "ARRES", IsNullable = true)] +[Serializable] +public class ApplyRewardsRequest { + [XmlElement(ElementName = "GID", IsNullable = false)] + public int GameID { get; set; } + + [XmlElement(ElementName = "LID", IsNullable = false)] + public int LevelID { get; set; } + + [XmlElement(ElementName = "LDID", IsNullable = false)] + public int LevelDifficultyID { get; set; } + + [XmlElement(ElementName = "LRT", IsNullable = false)] + public LevelRewardType LevelRewardType { get; set; } + + [XmlElement(ElementName = "RPEMS", IsNullable = false)] + public RaisedPetEntityMap[] RaisedPetEntityMaps { get; set; } + + [XmlElement(ElementName = "AGN", IsNullable = false)] + public Gender AvatarGender { get; set; } + + [XmlElement(ElementName = "LOC", IsNullable = true)] + public string Locale { get; set; } +} diff --git a/src/Schema/ApplyRewardsResponse.cs b/src/Schema/ApplyRewardsResponse.cs new file mode 100644 index 0000000..a206739 --- /dev/null +++ b/src/Schema/ApplyRewardsResponse.cs @@ -0,0 +1,22 @@ +using System.Xml.Serialization; + +namespace sodoff.Schema; + +[XmlRoot(ElementName = "ARR", Namespace = "")] +[Serializable] +public class ApplyRewardsResponse { + [XmlElement(ElementName = "ST", IsNullable = false)] + public Status Status { get; set; } + + [XmlElement(ElementName = "UID", IsNullable = false)] + public Guid UserID { get; set; } + + [XmlElement(ElementName = "ARS", IsNullable = false)] + public AchievementReward[] AchievementRewards { get; set; } + + [XmlElement(ElementName = "RISM", IsNullable = true)] + public UserItemStatsMap RewardedItemStatsMap { get; set; } + + [XmlElement(ElementName = "CIR", IsNullable = true)] + public CommonInventoryResponse CommonInventoryResponse { get; set; } +} diff --git a/src/Schema/BattleItemTierMap.cs b/src/Schema/BattleItemTierMap.cs new file mode 100644 index 0000000..1c57031 --- /dev/null +++ b/src/Schema/BattleItemTierMap.cs @@ -0,0 +1,19 @@ +using System.Xml.Serialization; + +namespace sodoff.Schema; + +[XmlRoot(ElementName = "BITM", Namespace = "")] +[Serializable] +public class BattleItemTierMap { + [XmlElement(ElementName = "IID", IsNullable = false)] + public int ItemID { get; set; } + + [XmlElement(ElementName = "T", IsNullable = true)] + public ItemTier? Tier { get; set; } + + [XmlElement(ElementName = "QTY", IsNullable = true)] + public int? Quantity { get; set; } + + [XmlElement(ElementName = "iss", IsNullable = true)] + public ItemStat[] ItemStats { get; set; } +} diff --git a/src/Schema/BluePrintFuseItemMap.cs b/src/Schema/BluePrintFuseItemMap.cs new file mode 100644 index 0000000..e645246 --- /dev/null +++ b/src/Schema/BluePrintFuseItemMap.cs @@ -0,0 +1,13 @@ +using System.Xml.Serialization; + +namespace sodoff.Schema; + +[XmlRoot(ElementName = "BPFIM", Namespace = "")] +[Serializable] +public class BluePrintFuseItemMap { + [XmlElement(ElementName = "BPSID", IsNullable = false)] + public int BluePrintSpecID { get; set; } + + [XmlElement(ElementName = "UID", IsNullable = false)] + public int UserInventoryID { get; set; } +} diff --git a/src/Schema/DeductibleItemInventoryMap.cs b/src/Schema/DeductibleItemInventoryMap.cs new file mode 100644 index 0000000..675da97 --- /dev/null +++ b/src/Schema/DeductibleItemInventoryMap.cs @@ -0,0 +1,16 @@ +using System.Xml.Serialization; + +namespace sodoff.Schema; + +[XmlRoot(ElementName = "DIIM", Namespace = "")] +[Serializable] +public class DeductibleItemInventoryMap { + [XmlElement(ElementName = "UID", IsNullable = false)] + public int UserInventoryID { get; set; } + + [XmlElement(ElementName = "IID", IsNullable = false)] + public int ItemID { get; set; } + + [XmlElement(ElementName = "QTY", IsNullable = false)] + public int Quantity { get; set; } +} diff --git a/src/Schema/FuseItemsRequest.cs b/src/Schema/FuseItemsRequest.cs new file mode 100644 index 0000000..9985c1f --- /dev/null +++ b/src/Schema/FuseItemsRequest.cs @@ -0,0 +1,26 @@ +using System.Xml.Serialization; + +namespace sodoff.Schema; + +[XmlRoot(ElementName = "FIR", Namespace = "")] +[Serializable] +public class FuseItemsRequest +{ + [XmlElement(ElementName = "BPINVID", IsNullable = true)] + public int? BluePrintInventoryID { get; set; } + + [XmlElement(ElementName = "BPIID", IsNullable = true)] + public int? BluePrintItemID { get; set; } + + [XmlElement(ElementName = "DIIM", IsNullable = true)] + public List DeductibleItemInventoryMaps { get; set; } + + [XmlElement(ElementName = "BPFIM", IsNullable = false)] + public List BluePrintFuseItemMaps { get; set; } + + [XmlElement(ElementName = "LOC", IsNullable = true)] + public string Locale { get; set; } + + [XmlElement(ElementName = "AGN", IsNullable = false)] + public Gender AvatarGender { get; set; } +} diff --git a/src/Schema/FuseItemsResponse.cs b/src/Schema/FuseItemsResponse.cs new file mode 100644 index 0000000..f1ef25a --- /dev/null +++ b/src/Schema/FuseItemsResponse.cs @@ -0,0 +1,20 @@ +using System.Xml.Serialization; + +namespace sodoff.Schema; + +[XmlRoot(ElementName = "FIRES", Namespace = "")] +[Serializable] +public class FuseItemsResponse +{ + [XmlElement(ElementName = "ST", IsNullable = false)] + public Status Status { get; set; } + + [XmlElement(ElementName = "UID", IsNullable = false)] + public Guid UserID { get; set; } + + [XmlElement(ElementName = "IISM", IsNullable = true)] + public List InventoryItemStatsMaps { get; set; } + + [XmlElement(ElementName = "VMSG", IsNullable = true)] + public ValidationMessage VMsg { get; set; } +} diff --git a/src/Schema/InventoryItemStatsMap.cs b/src/Schema/InventoryItemStatsMap.cs new file mode 100644 index 0000000..98b0ddc --- /dev/null +++ b/src/Schema/InventoryItemStatsMap.cs @@ -0,0 +1,16 @@ +using System.Xml.Serialization; + +namespace sodoff.Schema; + +[XmlRoot(ElementName = "IISM", Namespace = "")] +[Serializable] +public class InventoryItemStatsMap { + [XmlElement(ElementName = "CID", IsNullable = false)] + public int CommonInventoryID { get; set; } + + [XmlElement(ElementName = "ITM", IsNullable = false)] + public ItemData Item { get; set; } + + [XmlElement(ElementName = "ISM", IsNullable = false)] + public ItemStatsMap ItemStatsMap { get; set; } +} diff --git a/src/Schema/LevelRewardType.cs b/src/Schema/LevelRewardType.cs new file mode 100644 index 0000000..8df6c43 --- /dev/null +++ b/src/Schema/LevelRewardType.cs @@ -0,0 +1,16 @@ +using System.Xml.Serialization; + +namespace sodoff.Schema; + +[XmlRoot(ElementName = "GLRT")] +[Serializable] +public enum LevelRewardType { + [XmlEnum("1")] + LevelCompletion = 1, + + [XmlEnum("2")] + LevelFailure = 2, + + [XmlEnum("3")] + ExtraChest = 3 +} diff --git a/src/Schema/RaisedPetEntityMap.cs b/src/Schema/RaisedPetEntityMap.cs new file mode 100644 index 0000000..61077cd --- /dev/null +++ b/src/Schema/RaisedPetEntityMap.cs @@ -0,0 +1,14 @@ +using System.Xml.Serialization; + +namespace sodoff.Schema; + +[XmlRoot(ElementName = "RPEM", Namespace = "")] +[Serializable] +public class RaisedPetEntityMap +{ + [XmlElement(ElementName = "RPID")] + public int RaisedPetID { get; set; } + + [XmlElement(ElementName = "EID")] + public Guid? EntityID { get; set; } +} diff --git a/src/Schema/RollUserItemRequest.cs b/src/Schema/RollUserItemRequest.cs new file mode 100644 index 0000000..0aca7ec --- /dev/null +++ b/src/Schema/RollUserItemRequest.cs @@ -0,0 +1,20 @@ +using System.Xml.Serialization; + +namespace sodoff.Schema; + +[XmlRoot(ElementName = "RUIR", Namespace = "")] +[Serializable] +public class RollUserItemRequest +{ + [XmlElement(ElementName = "CID", IsNullable = false)] + public int ContainerID { get; set; } + + [XmlElement(ElementName = "UIID", IsNullable = false)] + public int UserInventoryID { get; set; } + + [XmlElement(ElementName = "ISN", IsNullable = true)] + public string[] ItemStatNames { get; set; } + + [XmlElement(ElementName = "CIR", IsNullable = false)] + public CommonInventoryRequest[] InventoryItems { get; set; } +} diff --git a/src/Schema/RollUserItemResponse.cs b/src/Schema/RollUserItemResponse.cs new file mode 100644 index 0000000..34d3172 --- /dev/null +++ b/src/Schema/RollUserItemResponse.cs @@ -0,0 +1,14 @@ +using System.Xml.Serialization; + +namespace sodoff.Schema; + +[XmlRoot(ElementName = "RUIRES", Namespace = "")] +[Serializable] +public class RollUserItemResponse +{ + [XmlElement(ElementName = "ST", IsNullable = false)] + public Status Status { get; set; } + + [XmlElement(ElementName = "IS", IsNullable = false)] + public ItemStat[] ItemStats { get; set; } +} diff --git a/src/Schema/SellItemsRequest.cs b/src/Schema/SellItemsRequest.cs new file mode 100644 index 0000000..1a2e042 --- /dev/null +++ b/src/Schema/SellItemsRequest.cs @@ -0,0 +1,14 @@ +using System.Xml.Serialization; + +namespace sodoff.Schema; + +[XmlRoot(ElementName = "SIR", Namespace = "")] +[Serializable] +public class SellItemsRequest +{ + [XmlElement(ElementName = "CID", IsNullable = false)] + public int ContainerID { get; set; } + + [XmlElement(ElementName = "UICDS", IsNullable = false)] + public int[] UserInventoryCommonIDs { get; set; } +} diff --git a/src/Schema/Status.cs b/src/Schema/Status.cs new file mode 100644 index 0000000..290227d --- /dev/null +++ b/src/Schema/Status.cs @@ -0,0 +1,73 @@ +using System.Xml.Serialization; + +namespace sodoff.Schema; + +[XmlRoot(ElementName = "ST")] +[Serializable] +public enum Status { + [XmlEnum("1")] + Success = 1, + + [XmlEnum("2")] + Failure, + + [XmlEnum("3")] + ItemNotFound, + + [XmlEnum("4")] + PoolItemsNotFound, + + [XmlEnum("5")] + InvalidItemMap, + + [XmlEnum("6")] + ItemNotFiltered, + + [XmlEnum("7")] + InvalidInput, + + [XmlEnum("8")] + InvalidGameMetaData, + + [XmlEnum("9")] + InvalidItemRarity, + + [XmlEnum("10")] + ItemStatsPersistFailed, + + [XmlEnum("11")] + InvalidItemPayout, + + [XmlEnum("12")] + InvalidStatsMap, + + [XmlEnum("13")] + InvalidPossibleStatsMap, + + [XmlEnum("14")] + ItemStatsNotExist, + + [XmlEnum("15")] + ItemsNotMapped, + + [XmlEnum("16")] + EventDataNotFound, + + [XmlEnum("17")] + MissionDataNotFound, + + [XmlEnum("18")] + SeasonRewardsNotFound, + + [XmlEnum("19")] + ItemNotFoundInInventory, + + [XmlEnum("20")] + BluePrintItemNotFound, + + [XmlEnum("21")] + LowDeductibleItemQuantity, + + [XmlEnum("22")] + InvalidBlueprintIngredients +} diff --git a/src/Schema/UserItemStatsMap.cs b/src/Schema/UserItemStatsMap.cs new file mode 100644 index 0000000..00f9a20 --- /dev/null +++ b/src/Schema/UserItemStatsMap.cs @@ -0,0 +1,25 @@ +using System.Xml.Serialization; + +namespace sodoff.Schema; + +[XmlRoot(ElementName = "UISM", Namespace = "")] +[Serializable] +public class UserItemStatsMap { + [XmlElement(ElementName = "UISMID", IsNullable = false)] + public int UserItemStatsMapID { get; set; } + + [XmlElement(ElementName = "UID")] + public Guid UserID { get; set; } + + [XmlElement(ElementName = "ITM", IsNullable = false)] + public ItemData Item { get; set; } + + [XmlElement(ElementName = "ISS", IsNullable = false)] + public ItemStat[] ItemStats { get; set; } + + [XmlElement(ElementName = "IT", IsNullable = true)] + public ItemTier? ItemTier { get; set; } + + [XmlElement(ElementName = "CD", IsNullable = true)] + public DateTime? CreatedDate { get; set; } +} diff --git a/src/Schema/ValidationMessage.cs b/src/Schema/ValidationMessage.cs new file mode 100644 index 0000000..e81e0b1 --- /dev/null +++ b/src/Schema/ValidationMessage.cs @@ -0,0 +1,14 @@ +using System.Xml.Serialization; + +namespace sodoff.Schema; + +[XmlRoot(ElementName = "VMSG", Namespace = "")] +[Serializable] +public class ValidationMessage +{ + [XmlElement(ElementName = "ST", IsNullable = false)] + public Status Status { get; set; } + + [XmlElement(ElementName = "EM", IsNullable = false)] + public string Message { get; set; } +} diff --git a/src/Services/AchievementService.cs b/src/Services/AchievementService.cs index d5a619e..5f7309a 100644 --- a/src/Services/AchievementService.cs +++ b/src/Services/AchievementService.cs @@ -7,6 +7,7 @@ using System.Xml.Linq; namespace sodoff.Services { public class AchievementService { + private InventoryService inventoryService; Dictionary ranks = new(); Dictionary achivmentsRewardByID = new(); @@ -15,7 +16,9 @@ namespace sodoff.Services { int dragonAdultMinXP; int dragonTitanMinXP; - public AchievementService() { + public AchievementService(InventoryService inventoryService) { + this.inventoryService = inventoryService; + ArrayOfUserRank allranks = XmlUtil.DeserializeXml(XmlUtil.ReadResourceXmlString("allranks")); foreach (var pointType in Enum.GetValues()) { ranks[pointType] = allranks.UserRank.Where(r => r.PointTypeID == pointType).ToArray(); @@ -100,24 +103,28 @@ namespace sodoff.Services { } } + public AchievementReward AddAchievementPointsAndGetReward(Viking viking, AchievementPointTypes type, int value) { + AddAchievementPoints(viking, type, value); + return new AchievementReward{ + EntityID = Guid.Parse(viking.Id), + PointTypeID = type, + EntityTypeID = 1, // player ? + RewardID = 1265, // TODO: placeholder + Amount = value + }; + } + public void ApplyAchievementReward(Viking viking, AchievementReward reward) { 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) { - ii = new InventoryItem { - ItemId = reward.ItemID, - Quantity = 0 - }; - viking.Inventory.InventoryItems.Add(ii); - } - ii.Quantity += (int)reward.Amount!; + inventoryService.AddItemToInventory(viking, reward.ItemID, (int)reward.Amount!); } else { // currencies, all types of player XP and dragon XP AddAchievementPoints(viking, reward.PointTypeID, reward.Amount); } } public AchievementReward[] ApplyAchievementRewards(Viking viking, AchievementReward[] rewards) { + if (rewards is null) + return null; foreach (var reward in rewards) { ApplyAchievementReward(viking, reward); /* TODO we don't need this? diff --git a/src/Services/InventoryService.cs b/src/Services/InventoryService.cs new file mode 100644 index 0000000..71bf426 --- /dev/null +++ b/src/Services/InventoryService.cs @@ -0,0 +1,115 @@ +using sodoff.Model; +using sodoff.Schema; +using sodoff.Util; +using System.Threading.Tasks; + +namespace sodoff.Services { + public class InventoryService { + + private readonly DBContext ctx; + private ItemService itemService; + + public const int Shards = 13711; + + public InventoryService(DBContext ctx, ItemService itemService) { + this.ctx = ctx; + this.itemService = itemService; + } + + public InventoryItem AddItemToInventory(Viking viking, int itemID, int quantity) { + InventoryItem? item = null; + if (!ItemNeedUniqueInventorySlot(itemID)) + item = viking.Inventory.InventoryItems.FirstOrDefault(e => e.ItemId == itemID); + if (item is null) { + item = new InventoryItem { + ItemId = itemID, + Quantity = 0 + }; + viking.Inventory.InventoryItems.Add(item); + } + item.Quantity += quantity; + return item; + } + + public CommonInventoryResponseItem AddItemToInventoryAndGetResponse(Viking viking, int itemID, int quantity) { + InventoryItem item = AddItemToInventory(viking, itemID, quantity); + + ctx.SaveChanges(); // We need to get the ID of the newly created item + + if (quantity == 1) + quantity = 0; // The game expects 0 if quantity got updated by just 1 + // Otherwise it expects the quantity from the request + return new CommonInventoryResponseItem { + CommonInventoryID = item.Id, + ItemID = itemID, + Quantity = quantity + }; + } + + public InventoryItemStatsMap AddBattleItemToInventory(Viking viking, int itemId, int itemTier, ItemStat[] itemStat = null) { + // get item data + ItemData itemData = itemService.GetItem(itemId); + + // create new item + InventoryItem item = new InventoryItem { ItemId = itemId, Quantity = 1 }; + ItemStatsMap itemStatsMap = new ItemStatsMap { + ItemID = itemId, + ItemTier = (ItemTier)itemTier, + ItemStats = itemStat ?? itemService.CreateItemStats(itemData.PossibleStatsMap, (int)itemData.ItemRarity, itemTier).ToArray() + }; + item.StatsSerialized = XmlUtil.SerializeXml(itemStatsMap); + + // add to viking + viking.Inventory.InventoryItems.Add(item); + ctx.SaveChanges(); // We need to get the ID of the newly created item + + // return item with stats + return new InventoryItemStatsMap { + CommonInventoryID = item.Id, + Item = itemData, + ItemStatsMap = itemStatsMap + }; + } + + public CommonInventoryData GetCommonInventoryData(Viking viking) { + List items = viking.Inventory.InventoryItems.ToList(); + + List userItemData = new(); + foreach (InventoryItem item in items) { + if (item.Quantity == 0) continue; // Don't include an item that the viking doesn't have + ItemData itemData = itemService.GetItem(item.ItemId); + UserItemData uid = new UserItemData { + UserInventoryID = item.Id, + ItemID = itemData.ItemID, + Quantity = item.Quantity, + Uses = itemData.Uses, + ModifiedDate = new DateTime(DateTime.Now.Ticks), + Item = itemData + }; + if (item.StatsSerialized != null) { + ItemStatsMap itemStats = XmlUtil.DeserializeXml(item.StatsSerialized); + uid.ItemStats = itemStats.ItemStats; + uid.ItemTier = itemStats.ItemTier; + } else if (itemData.ItemStatsMap != null) { + uid.ItemStats = itemData.ItemStatsMap?.ItemStats; + uid.ItemTier = itemData.ItemStatsMap?.ItemTier; + } + userItemData.Add(uid); + } + + return new CommonInventoryData { + UserID = Guid.Parse(viking.Id), + Item = userItemData.ToArray() + }; + } + + public bool ItemNeedUniqueInventorySlot(int itemId) { + return itemService.ItemHasCategory( + itemService.GetItem(itemId), new int[] { + 541, // farm expansion + 511, // dragons tactics (battle) items + } + ); + } + } +} diff --git a/src/Services/ItemService.cs b/src/Services/ItemService.cs index 6f4d7c6..84b79ae 100644 --- a/src/Services/ItemService.cs +++ b/src/Services/ItemService.cs @@ -8,16 +8,86 @@ namespace sodoff.Services { public class ItemService { Dictionary items = new(); + int[] itemsRewardForDT; + Random random = new Random(); public ItemService() { ServerItemArray itemArray = XmlUtil.DeserializeXml(XmlUtil.ReadResourceXmlString("items")); foreach (var item in itemArray.ItemDataArray) { items.Add(item.ItemID, item); } + + itemsRewardForDT = XmlUtil.DeserializeXml(XmlUtil.ReadResourceXmlString("dtrewards")); } public ItemData GetItem(int id) { return items[id]; } + + public ItemData GetDTReward() { + // TODO: better calculation of reward item - use difficulty of DT level, item rarity, tier, etc + int itemID = itemsRewardForDT[random.Next(0, itemsRewardForDT.Length)]; + return items[itemID]; + } + + public ItemDataRelationship OpenBox(ItemData boxItem) { + var boxRewards = boxItem.Relationship.Where(e => e.Type == "Prize").ToArray(); + int totalWeight = boxRewards.Sum(e => e.Weight); + if (totalWeight == 0) { + return boxRewards[random.Next(0, boxRewards.Length)]; + } + int cnt = 0; + int win = random.Next(0, totalWeight); + foreach (var reward in boxRewards) { + cnt += reward.Weight; + if (cnt > win) { + return reward; + } + } + return null; + } + + public bool ItemHasCategory(ItemData itemData, int categoryId) { + ItemDataCategory? category = itemData.Category?.FirstOrDefault(e => e.CategoryId == categoryId); + return category != null; + } + + public bool ItemHasCategory(ItemData itemData, int[] categoryIds) { + ItemDataCategory? category = itemData.Category?.FirstOrDefault(e => categoryIds.Contains(e.CategoryId)); + return category != null; + } + + public List CreateItemStats(ItemPossibleStatsMap? possibleStats, int rarity, int itemTier) { + List newStat = new List(); + int rMax = possibleStats.Stats.Sum(e => e.Probability); + for (int i=0; i 0; ++i) { + int val = random.Next(0, rMax); + int cnt = 0; + foreach (var stat in possibleStats.Stats) { + if (newStat.FirstOrDefault(e => e.ItemStatID == stat.ItemStatsID) != null) { + // this type of stat already is in newStat ... is excluded from this draw + continue; + } + + cnt += stat.Probability; + if (cnt > val) { + rMax -= stat.Probability; // do not include in the next draw + + StatRangeMap rangeMap = stat.ItemStatsRangeMaps.FirstOrDefault(e => e.ItemTierID == itemTier); + newStat.Add( + new ItemStat { + ItemStatID = rangeMap.ItemStatsID, + Name = rangeMap.ItemStatsName, + Value = random.Next(rangeMap.StartRange, rangeMap.EndRange+1).ToString(), + DataType = DataTypeInfo.Int + } + ); + + break; // we found new stat for slot "i" ... goto i+1 + } + } + } + return newStat; + } } } diff --git a/src/sodoff.csproj b/src/sodoff.csproj index 3d46ca7..9aaef0d 100644 --- a/src/sodoff.csproj +++ b/src/sodoff.csproj @@ -43,6 +43,9 @@ PreserveNewest + + PreserveNewest + @@ -66,6 +69,9 @@ PreserveNewest + + PreserveNewest + PreserveNewest