dragons tactics (battle) backpack (#26)

* return battle item as battle items

placeholder - no saved stats, always use default stats

* Blacksmith API implementation

* ApplyRewards (DT rewards) and AddBattleItems

also temporary disable some AchievementsTask rewards from DT ... probably should not be repeatable ...

* support "box tickets" blueprints

- add OpenBox in ItemService (should be useful for other boxes also)
- move checking item category to ItemService (ItemHasCategory)

* InventoryService and full DragonTactics rewards

- move "add to inventory" to InventoryService
- move creating CommonInventoryData from inventory to InventoryService
- move CreateItemStats() to ItemService
- assigning random "item" reward from DT based on dtrewards.xml (at this moment list of all DT items not listed in store)
- rework dragon XP and add player XP and gems reward to DT
This commit is contained in:
rpaciorek 2023-08-31 18:42:03 +00:00 committed by GitHub
parent abe674ccf8
commit 7bee6c620a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
29 changed files with 1259 additions and 129 deletions

View File

@ -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)

View File

@ -70,6 +70,11 @@ methods = [
'GetUserGameCurrency',
'SetAchievementByEntityIDs',
'UseInventory',
'SellItems',
'FuseItems',
'RerollUserItem',
'AddBattleItems',
'ApplyRewards',
'DeleteProfile',
'DeleteAccountNotification',
'SetUserAchievementAndGetReward',

View File

@ -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> userItemData = new();
List<UserItemData> userItemData;
if (viking != null) {
List<InventoryItem> 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<InventoryItem> items = viking.Inventory.InventoryItems.ToList();
List<UserItemData> 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<req.Quantity; ++i) {
InventoryItem item = new InventoryItem { ItemId = (int)req.ItemID, Quantity = 1 };
viking.Inventory.InventoryItems.Add(item);
ctx.SaveChanges(); // We need to get the ID of the newly created item
responseItems.Add(new CommonInventoryResponseItem {
CommonInventoryID = item.Id,
ItemID = item.ItemId,
Quantity = 0
});
responseItems.Add(
inventoryService.AddItemToInventoryAndGetResponse(viking, (int)req.ItemID!, 1)
);
}
} else {
InventoryItem? item = viking.Inventory.InventoryItems.FirstOrDefault(e => 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<PurchaseStoreItemRequest>(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<int[]>(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<RollUserItemRequest>(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<ItemStatsMap>(invItem.StatsSerialized);
} else {
itemStatsMap = itemData.ItemStatsMap;
}
List<ItemStat> newStats;
Status status = Status.Failure;
// update stats
if (req.ItemStatNames != null) {
// reroll only one stat (from req.ItemStatNames)
newStats = new List<ItemStat>();
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>(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<InventoryItemStatsMap>();
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>(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<AddBattleItemsRequest>(request);
var resItemList = new List<InventoryItemStatsMap>();
foreach (BattleItemTierMap battleItemTierMap in req.BattleItemTierMaps) {
for (int i=0; i<battleItemTierMap.Quantity; ++i) {
resItemList.Add(
inventoryService.AddBattleItemToInventory(viking, battleItemTierMap.ItemID, (int)battleItemTierMap.Tier, battleItemTierMap.ItemStats)
// NOTE: battleItemTierMap.ItemStats is extension for importer
);
}
}
return Ok(new AddBattleItemsResponse{
Status = Status.Success,
InventoryItemStatsMaps = resItemList
});
}
[HttpPost]
[Produces("application/xml")]
[Route("V2/ContentWebService.asmx/ApplyRewards")]
public IActionResult ApplyRewards([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();
ApplyRewardsRequest req = XmlUtil.DeserializeXml<ApplyRewardsRequest>(request);
List<AchievementReward> achievementRewards = new List<AchievementReward>();
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<RaisedPetData>(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<string> suggestions) {
if (ctx.Vikings.Any(x => x.Name == name) || suggestions.Contains(name)) {
name += rand.Next(1, 5000);

View File

@ -9,6 +9,8 @@ namespace sodoff.Model {
public int InventoryId { get; set; }
public string? StatsSerialized { get; set; }
public virtual Inventory Inventory { get; set; }
public int Quantity { get; set; }

View File

@ -19,6 +19,7 @@ builder.Services.AddScoped<MissionService>();
builder.Services.AddSingleton<StoreService>();
builder.Services.AddScoped<RoomService>();
builder.Services.AddSingleton<AchievementService>();
builder.Services.AddScoped<InventoryService>();
var app = builder.Build();

View File

@ -517,6 +517,8 @@
</AchievementsTaskInfo>
<AchievementsTaskInfo>
<TID>2261</TID>
<!-- defeated enemy in dt -->
<!-- TODO: temporary disabled - this probably should not be repeated for every event
<AR>
<a>300</a>
<p>1</p>
@ -537,10 +539,12 @@
<t>1</t>
<r>937</r>
<ii>0</ii>
</AR>
</AR> -->
</AchievementsTaskInfo>
<AchievementsTaskInfo>
<TID>2262</TID>
<!-- ?? in DT -->
<!-- TODO: temporary disabled - this probably should not be repeated for every event
<AR>
<a>10</a>
<p>12</p>
@ -568,7 +572,7 @@
<t>1</t>
<r>937</r>
<ii>0</ii>
</AR>
</AR> -->
</AchievementsTaskInfo>
<AchievementsTaskInfo>
<TID>2263</TID>
@ -610,6 +614,7 @@
</AchievementsTaskInfo>
<AchievementsTaskInfo>
<TID>2264</TID>
<!-- ?? in DT -->
<AR>
<a>100</a>
<p>1</p>
@ -658,7 +663,9 @@
</AchievementsTaskInfo>
<AchievementsTaskInfo>
<TID>2267</TID>
<!-- winning DT -->
<!-- can return higher (10 times!) rewards also -->
<!-- TODO: temporary disabled - this probably should not be repeated for every event
<AR>
<a>20</a>
<p>1</p>
@ -686,11 +693,13 @@
<t>1</t>
<r>937</r>
<ii>0</ii>
</AR>
</AR> -->
</AchievementsTaskInfo>
<AchievementsTaskInfo>
<TID>2268</TID>
<!-- winning DT -->
<!-- can return higher rewards also, for example 340 dragon XP and 330 player XP -->
<!-- TODO: temporary disabled - this probably should not be repeated for every event
<AR>
<a>30</a>
<p>1</p>
@ -718,7 +727,11 @@
<t>1</t>
<r>937</r>
<ii>0</ii>
</AR>
</AR> -->
</AchievementsTaskInfo>
<AchievementsTaskInfo>
<TID>2269</TID>
<!-- losing DT -->
</AchievementsTaskInfo>
<AchievementsTaskInfo>
<TID>2275</TID>

309
src/Resources/dtrewards.xml Normal file
View File

@ -0,0 +1,309 @@
<?xml version="1.0" encoding="UTF-8"?>
<ArrayOfInt xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<int>12374</int>
<int>12375</int>
<int>12377</int>
<int>12378</int>
<int>12379</int>
<int>12380</int>
<int>12381</int>
<int>12382</int>
<int>12383</int>
<int>12392</int>
<int>12393</int>
<int>12394</int>
<int>12396</int>
<int>12397</int>
<int>12398</int>
<int>13660</int>
<int>13661</int>
<int>13684</int>
<int>13685</int>
<int>13686</int>
<int>13687</int>
<int>13700</int>
<int>13701</int>
<int>13718</int>
<int>13719</int>
<int>13720</int>
<int>13721</int>
<int>13722</int>
<int>13723</int>
<int>13724</int>
<int>13725</int>
<int>13726</int>
<int>13727</int>
<int>13728</int>
<int>13747</int>
<int>13748</int>
<int>13749</int>
<int>13750</int>
<int>13751</int>
<int>13752</int>
<int>13753</int>
<int>13754</int>
<int>13755</int>
<int>13757</int>
<int>13758</int>
<int>13759</int>
<int>13760</int>
<int>13761</int>
<int>13762</int>
<int>13763</int>
<int>13764</int>
<int>13765</int>
<int>13766</int>
<int>13767</int>
<int>13768</int>
<int>13769</int>
<int>13770</int>
<int>13771</int>
<int>13772</int>
<int>13773</int>
<int>14842</int>
<int>14843</int>
<int>14844</int>
<int>14845</int>
<int>14846</int>
<int>14847</int>
<int>14848</int>
<int>14849</int>
<int>14850</int>
<int>14851</int>
<int>14852</int>
<int>14853</int>
<int>14854</int>
<int>14855</int>
<int>14856</int>
<int>14857</int>
<int>14858</int>
<int>14859</int>
<int>14860</int>
<int>14861</int>
<int>14862</int>
<int>14863</int>
<int>14864</int>
<int>14865</int>
<int>14866</int>
<int>14867</int>
<int>14868</int>
<int>14869</int>
<int>14870</int>
<int>14871</int>
<int>14873</int>
<int>14874</int>
<int>14875</int>
<int>14876</int>
<int>14877</int>
<int>14878</int>
<int>14879</int>
<int>14880</int>
<int>14881</int>
<int>14882</int>
<int>14883</int>
<int>14908</int>
<int>14921</int>
<int>14923</int>
<int>14933</int>
<int>15778</int>
<int>15781</int>
<int>15784</int>
<int>15785</int>
<int>15899</int>
<int>16249</int>
<int>16669</int>
<int>16670</int>
<int>16671</int>
<int>16729</int>
<int>16730</int>
<int>16731</int>
<int>16735</int>
<int>16806</int>
<int>17864</int>
<int>17865</int>
<int>17885</int>
<int>17886</int>
<int>17943</int>
<int>17997</int>
<int>18064</int>
<int>18099</int>
<int>18111</int>
<int>18274</int>
<int>18276</int>
<int>18278</int>
<int>18289</int>
<int>18290</int>
<int>18291</int>
<int>18339</int>
<int>18340</int>
<int>18467</int>
<int>18468</int>
<int>18482</int>
<int>18483</int>
<int>18489</int>
<int>18490</int>
<int>18491</int>
<int>18492</int>
<int>18493</int>
<int>18494</int>
<int>18495</int>
<int>18496</int>
<int>18497</int>
<int>18498</int>
<int>18501</int>
<int>18751</int>
<int>18752</int>
<int>18779</int>
<int>18780</int>
<int>18781</int>
<int>18782</int>
<int>18783</int>
<int>18784</int>
<int>18785</int>
<int>18812</int>
<int>18813</int>
<int>18814</int>
<int>19002</int>
<int>19021</int>
<int>19022</int>
<int>19023</int>
<int>19024</int>
<int>19025</int>
<int>19026</int>
<int>19027</int>
<int>19028</int>
<int>19029</int>
<int>19030</int>
<int>19046</int>
<int>19047</int>
<int>19112</int>
<int>19113</int>
<int>19181</int>
<int>20261</int>
<int>20262</int>
<int>20263</int>
<int>20304</int>
<int>20476</int>
<int>20477</int>
<int>20478</int>
<int>20492</int>
<int>20493</int>
<int>20496</int>
<int>20497</int>
<int>20498</int>
<int>20499</int>
<int>20500</int>
<int>20501</int>
<int>20502</int>
<int>20503</int>
<int>20504</int>
<int>20505</int>
<int>20506</int>
<int>20507</int>
<int>20508</int>
<int>20509</int>
<int>20510</int>
<int>20511</int>
<int>20512</int>
<int>20513</int>
<int>20515</int>
<int>20516</int>
<int>20517</int>
<int>20518</int>
<int>20519</int>
<int>20520</int>
<int>20521</int>
<int>20522</int>
<int>20523</int>
<int>20524</int>
<int>20525</int>
<int>20526</int>
<int>20527</int>
<int>20528</int>
<int>20529</int>
<int>20530</int>
<int>20531</int>
<int>20532</int>
<int>20533</int>
<int>20534</int>
<int>20535</int>
<int>20536</int>
<int>20537</int>
<int>20538</int>
<int>20539</int>
<int>20540</int>
<int>20541</int>
<int>20542</int>
<int>20543</int>
<int>20544</int>
<int>20545</int>
<int>20546</int>
<int>20547</int>
<int>20548</int>
<int>20549</int>
<int>20550</int>
<int>20551</int>
<int>20552</int>
<int>20553</int>
<int>20554</int>
<int>20555</int>
<int>20556</int>
<int>20557</int>
<int>20558</int>
<int>20559</int>
<int>20560</int>
<int>20561</int>
<int>20562</int>
<int>20563</int>
<int>20564</int>
<int>20565</int>
<int>20566</int>
<int>20567</int>
<int>20568</int>
<int>20569</int>
<int>20570</int>
<int>20571</int>
<int>20572</int>
<int>20573</int>
<int>20574</int>
<int>20575</int>
<int>20576</int>
<int>20577</int>
<int>20578</int>
<int>20579</int>
<int>20580</int>
<int>20581</int>
<int>20582</int>
<int>20583</int>
<int>20584</int>
<int>20585</int>
<int>20586</int>
<int>20587</int>
<int>20588</int>
<int>20589</int>
<int>20590</int>
<int>20591</int>
<int>20592</int>
<int>20593</int>
<int>20594</int>
<int>20595</int>
<int>20596</int>
<int>20597</int>
<int>20598</int>
<int>20599</int>
<int>20600</int>
<int>20601</int>
<int>20602</int>
<int>20603</int>
<int>20604</int>
<int>20605</int>
<int>20606</int>
<int>20607</int>
<int>20608</int>
<int>20609</int>
<int>20610</int>
<int>20857</int>
<int>20858</int>
<int>7149</int>
<int>7150</int>
</ArrayOfInt>

View File

@ -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<BattleItemTierMap> BattleItemTierMaps { get; set; }
}

View File

@ -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<InventoryItemStatsMap> InventoryItemStatsMaps { get; set; }
}

View File

@ -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; }
}

View File

@ -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; }
}

View File

@ -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; }
}

View File

@ -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; }
}

View File

@ -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; }
}

View File

@ -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<DeductibleItemInventoryMap> DeductibleItemInventoryMaps { get; set; }
[XmlElement(ElementName = "BPFIM", IsNullable = false)]
public List<BluePrintFuseItemMap> BluePrintFuseItemMaps { get; set; }
[XmlElement(ElementName = "LOC", IsNullable = true)]
public string Locale { get; set; }
[XmlElement(ElementName = "AGN", IsNullable = false)]
public Gender AvatarGender { get; set; }
}

View File

@ -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<InventoryItemStatsMap> InventoryItemStatsMaps { get; set; }
[XmlElement(ElementName = "VMSG", IsNullable = true)]
public ValidationMessage VMsg { get; set; }
}

View File

@ -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; }
}

View File

@ -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
}

View File

@ -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; }
}

View File

@ -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; }
}

View File

@ -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; }
}

View File

@ -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; }
}

73
src/Schema/Status.cs Normal file
View File

@ -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
}

View File

@ -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; }
}

View File

@ -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; }
}

View File

@ -7,6 +7,7 @@ using System.Xml.Linq;
namespace sodoff.Services {
public class AchievementService {
private InventoryService inventoryService;
Dictionary<AchievementPointTypes, UserRank[]> ranks = new();
Dictionary<int, AchievementReward[]> 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<ArrayOfUserRank>(XmlUtil.ReadResourceXmlString("allranks"));
foreach (var pointType in Enum.GetValues<AchievementPointTypes>()) {
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?

View File

@ -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<InventoryItem> items = viking.Inventory.InventoryItems.ToList();
List<UserItemData> 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<ItemStatsMap>(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
}
);
}
}
}

View File

@ -8,16 +8,86 @@ namespace sodoff.Services {
public class ItemService {
Dictionary<int, ItemData> items = new();
int[] itemsRewardForDT;
Random random = new Random();
public ItemService() {
ServerItemArray itemArray = XmlUtil.DeserializeXml<ServerItemArray>(XmlUtil.ReadResourceXmlString("items"));
foreach (var item in itemArray.ItemDataArray) {
items.Add(item.ItemID, item);
}
itemsRewardForDT = XmlUtil.DeserializeXml<int[]>(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<ItemStat> CreateItemStats(ItemPossibleStatsMap? possibleStats, int rarity, int itemTier) {
List<ItemStat> newStat = new List<ItemStat>();
int rMax = possibleStats.Stats.Sum(e => e.Probability);
for (int i=0; i<rarity && rMax > 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;
}
}
}

View File

@ -43,6 +43,9 @@
<None Update="Resources\achievementstasks.xml">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Resources\dtrewards.xml">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Resources\rewardmultiplier.xml">
@ -66,6 +69,9 @@
<EmbeddedResource Include="Resources\achievementstasks.xml">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</EmbeddedResource>
<EmbeddedResource Include="Resources\dtrewards.xml">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</EmbeddedResource>
<EmbeddedResource Include="Resources\items.xml">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</EmbeddedResource>