public void UpdateTradableItem_Material_Multiple_Slot() { var row = TableSheets.MaterialItemSheet.First; Assert.NotNull(row); var tradableId = TradableMaterial.DeriveTradableId(row.ItemId); var inventory = new Inventory(); for (var i = 0; i < 2; i++) { ITradableItem tradableItem = ItemFactory.CreateTradableMaterial(row); tradableItem.RequiredBlockIndex = i; inventory.AddItem((ItemBase)tradableItem, 2); } Assert.Equal(2, inventory.Items.Count); var result = inventory.UpdateTradableItem(tradableId, 0, 1, 1); Assert.Equal(2, inventory.Items.Count); Assert.Equal(1, result.RequiredBlockIndex); Assert.True(inventory.TryGetTradableItems(tradableId, 1, 4, out var items)); Assert.Equal(2, items.Count); for (var i = 0; i < items.Count; i++) { var item = items[i]; Assert.Equal(2 * i + 1, item.count); Assert.Equal(i, ((ITradableItem)item.item).RequiredBlockIndex); } }
public void TryGetTradableItems_Material_Multiple_Slot() { var row = TableSheets.MaterialItemSheet.First; Assert.NotNull(row); var inventory = new Inventory(); for (var i = 0; i < 2; i++) { var tradableItem = ItemFactory.CreateTradableMaterial(row); tradableItem.RequiredBlockIndex = i; inventory.AddItem(tradableItem, 1); } Assert.Equal(2, inventory.Items.Count); inventory.TryGetTradableItems(TradableMaterial.DeriveTradableId(row.ItemId), 1, 2, out var items); Assert.Equal(2, items.Count); for (var index = 0; index < items.Count; index++) { var item = items[index]; Assert.Equal(1, item.count); var tradableItem = (ITradableItem)item.item; Assert.Equal(index, tradableItem.RequiredBlockIndex); } }
public void SellItem_Material_Multiple_Slot(int totalCount, int sellCount, int expectedCount) { var inventory = new Inventory(); var row = TableSheets.MaterialItemSheet.First; Assert.NotNull(row); for (var i = 1; i < totalCount + 1; i++) { var tradableItem = ItemFactory.CreateTradableMaterial(row); tradableItem.RequiredBlockIndex = i; inventory.AddItem(tradableItem, 1); Assert.True(inventory.TryGetTradableItems(tradableItem.TradableId, i, i, out _)); } Assert.Equal(totalCount, inventory.Items.Count); Assert.True(inventory.HasItem(row.Id, totalCount)); inventory.SellItem(TradableMaterial.DeriveTradableId(row.ItemId), totalCount, sellCount); Assert.Equal(expectedCount, inventory.Items.Count); }
public override IAccountStateDelta Execute(IActionContext context) { var states = context.PreviousStates; if (context.Rehearsal) { states = states.SetState(sellerAvatarAddress, MarkChanged); states = ShardedShopState.AddressKeys.Aggregate( states, (current, addressKey) => current.SetState( ShardedShopState.DeriveAddress(itemSubType, addressKey), MarkChanged)); return(states.SetState(context.Signer, MarkChanged)); } CheckObsolete(BlockChain.Policy.BlockPolicySource.V100080ObsoleteIndex, context); var addressesHex = GetSignerAndOtherAddressesHex(context, sellerAvatarAddress); var sw = new Stopwatch(); sw.Start(); var started = DateTimeOffset.UtcNow; Log.Verbose("{AddressesHex}Sell exec started", addressesHex); if (price.Sign < 0) { throw new InvalidPriceException( $"{addressesHex}Aborted as the price is less than zero: {price}."); } if (!states.TryGetAgentAvatarStates( context.Signer, sellerAvatarAddress, out _, out var avatarState)) { throw new FailedLoadStateException( $"{addressesHex}Aborted as the avatar state of the signer was failed to load."); } sw.Stop(); Log.Verbose( "{AddressesHex}Sell Get AgentAvatarStates: {Elapsed}", addressesHex, sw.Elapsed); sw.Restart(); if (!avatarState.worldInformation.IsStageCleared( GameConfig.RequireClearedStageLevel.ActionsInShop)) { avatarState.worldInformation.TryGetLastClearedStageId(out var current); throw new NotEnoughClearedStageLevelException( addressesHex, GameConfig.RequireClearedStageLevel.ActionsInShop, current); } sw.Stop(); Log.Verbose("{AddressesHex}Sell IsStageCleared: {Elapsed}", addressesHex, sw.Elapsed); sw.Restart(); switch (itemSubType) { case ItemSubType.EquipmentMaterial: case ItemSubType.FoodMaterial: case ItemSubType.MonsterPart: case ItemSubType.NormalMaterial: throw new InvalidShopItemException( $"{addressesHex}Aborted because {nameof(itemSubType)}({itemSubType}) does not support."); } if (count < 1) { throw new InvalidShopItemException( $"{addressesHex}Aborted because {nameof(count)}({count}) should be greater than or equal to 1."); } if (!avatarState.inventory.TryGetTradableItems(tradableId, context.BlockIndex, count, out List <Inventory.Item> inventoryItems)) { throw new ItemDoesNotExistException( $"{addressesHex}Aborted because the tradable item({tradableId}) was failed to load from avatar's inventory."); } IEnumerable <ITradableItem> tradableItems = inventoryItems.Select(i => (ITradableItem)i.item).ToList(); var expiredBlockIndex = context.BlockIndex + ExpiredBlockIndex; foreach (var ti in tradableItems) { if (!ti.ItemSubType.Equals(itemSubType)) { throw new InvalidItemTypeException( $"{addressesHex}Expected ItemSubType: {ti.ItemSubType}. Actual ItemSubType: {itemSubType}"); } if (ti is INonFungibleItem) { if (count != 1) { throw new ArgumentOutOfRangeException( $"{addressesHex}Aborted because {nameof(count)}({count}) should be 1 because {nameof(tradableId)}({tradableId}) is non-fungible item."); } } } ITradableItem tradableItem = avatarState.inventory.SellItem(tradableId, context.BlockIndex, count); var productId = context.Random.GenerateRandomGuid(); var shardedShopAddress = ShardedShopState.DeriveAddress(itemSubType, productId); if (!states.TryGetState(shardedShopAddress, out BxDictionary serializedSharedShopState)) { var shardedShopState = new ShardedShopState(shardedShopAddress); serializedSharedShopState = (BxDictionary)shardedShopState.Serialize(); } sw.Stop(); Log.Verbose( "{AddressesHex}Sell Get ShardedShopState: {Elapsed}", addressesHex, sw.Elapsed); sw.Restart(); var serializedProductList = (BxList)serializedSharedShopState[ProductsKey]; string productKey; string itemIdKey; string requiredBlockIndexKey; switch (tradableItem.ItemType) { case ItemType.Consumable: case ItemType.Equipment: productKey = LegacyItemUsableKey; itemIdKey = LegacyItemIdKey; requiredBlockIndexKey = LegacyRequiredBlockIndexKey; break; case ItemType.Costume: productKey = LegacyCostumeKey; itemIdKey = LegacyCostumeItemIdKey; requiredBlockIndexKey = RequiredBlockIndexKey; break; case ItemType.Material: productKey = TradableFungibleItemKey; itemIdKey = LegacyCostumeItemIdKey; requiredBlockIndexKey = RequiredBlockIndexKey; break; default: throw new ArgumentOutOfRangeException(); } BxDictionary serializedProductDictionary; if (tradableItem.ItemType == ItemType.Material) { // Find expired TradableMaterial serializedProductDictionary = serializedProductList .Select(p => (BxDictionary)p) .FirstOrDefault(p => { var materialItemId = ((BxDictionary)p[productKey])[itemIdKey].ToItemId(); var requiredBlockIndex = p[ExpiredBlockIndexKey].ToLong(); return(TradableMaterial.DeriveTradableId(materialItemId) .Equals(tradableItem.TradableId) && requiredBlockIndex <= context.BlockIndex); }); } else { var serializedTradeId = tradableItem.TradableId.Serialize(); serializedProductDictionary = serializedProductList .Select(p => (BxDictionary)p) .FirstOrDefault(p => ((BxDictionary)p[productKey])[itemIdKey].Equals(serializedTradeId)); } // Since Bencodex 0.4, Dictionary/List are reference types; so their default values // are not a empty container, but a null reference: serializedProductDictionary = serializedProductDictionary ?? Dictionary.Empty; ShopItem shopItem; // Register new ShopItem if (serializedProductDictionary.Equals(BxDictionary.Empty)) { shopItem = new ShopItem( context.Signer, sellerAvatarAddress, productId, price, expiredBlockIndex, tradableItem, count); var serializedShopItem = shopItem.Serialize(); serializedProductList = serializedProductList.Add(serializedShopItem); } // Update Registered ShopItem else { // Delete current ShopItem serializedProductList = (BxList)serializedProductList.Remove(serializedProductDictionary); // Update ITradableItem.RequiredBlockIndex var inChainShopItem = (BxDictionary)serializedProductDictionary[productKey]; inChainShopItem = inChainShopItem .SetItem(requiredBlockIndexKey, expiredBlockIndex.Serialize()); // Update ShopItem.ExpiredBlockIndex serializedProductDictionary = serializedProductDictionary .SetItem(ExpiredBlockIndexKey, expiredBlockIndex.Serialize()) .SetItem(productKey, inChainShopItem); // Update only Material for backwardCompatible. if (tradableItem.ItemType == ItemType.Material) { serializedProductDictionary = serializedProductDictionary .SetItem(TradableFungibleItemCountKey, count.Serialize()); } serializedProductList = serializedProductList.Add(serializedProductDictionary); shopItem = new ShopItem(serializedProductDictionary); } serializedSharedShopState = serializedSharedShopState.SetItem( ProductsKey, new List <IValue>(serializedProductList)); sw.Stop(); Log.Verbose("{AddressesHex}Sell Get Register Item: {Elapsed}", addressesHex, sw.Elapsed); sw.Restart(); avatarState.updatedAt = context.BlockIndex; avatarState.blockIndex = context.BlockIndex; var result = new SellCancellation.Result { shopItem = shopItem, itemUsable = shopItem.ItemUsable, costume = shopItem.Costume, tradableFungibleItem = shopItem.TradableFungibleItem, tradableFungibleItemCount = shopItem.TradableFungibleItemCount, }; var mail = new SellCancelMail( result, context.BlockIndex, context.Random.GenerateRandomGuid(), expiredBlockIndex); result.id = mail.id; avatarState.Update(mail); states = states.SetState(sellerAvatarAddress, avatarState.Serialize()); sw.Stop(); Log.Verbose("{AddressesHex}Sell Set AvatarState: {Elapsed}", addressesHex, sw.Elapsed); sw.Restart(); states = states.SetState(shardedShopAddress, serializedSharedShopState); sw.Stop(); var ended = DateTimeOffset.UtcNow; Log.Verbose("{AddressesHex}Sell Set ShopState: {Elapsed}", addressesHex, sw.Elapsed); Log.Verbose( "{AddressesHex}Sell Total Executed Time: {Elapsed}", addressesHex, ended - started); return(states); }