public void Read(SellerMail sellerMail) { var agentAddress = States.Instance.AgentState.address; var avatarAddress = States.Instance.CurrentAvatarState.address; var attachment = (Buy.SellerResult)sellerMail.attachment; //TODO 관련 기획이 끝나면 별도 UI를 생성 LocalLayerModifier.ModifyAgentGold(agentAddress, attachment.gold); LocalLayerModifier.RemoveNewAttachmentMail(avatarAddress, sellerMail.id); }
public override IAccountStateDelta Execute(IActionContext context) { IActionContext ctx = context; var states = ctx.PreviousStates; if (ctx.Rehearsal) { states = states .SetState(buyerAvatarAddress, MarkChanged) .SetState(ctx.Signer, MarkChanged) .SetState(sellerAvatarAddress, MarkChanged) .MarkBalanceChanged( GoldCurrencyMock, ctx.Signer, sellerAgentAddress, GoldCurrencyState.Address); return(states.SetState(ShopState.Address, MarkChanged)); } var addressesHex = GetSignerAndOtherAddressesHex(context, buyerAvatarAddress, sellerAvatarAddress); if (ctx.Signer.Equals(sellerAgentAddress)) { throw new InvalidAddressException($"{addressesHex}Aborted as the signer is the seller."); } var sw = new Stopwatch(); sw.Start(); var started = DateTimeOffset.UtcNow; Log.Verbose("{AddressesHex}Buy exec started", addressesHex); if (!states.TryGetAvatarState(ctx.Signer, buyerAvatarAddress, out var buyerAvatarState)) { throw new FailedLoadStateException($"{addressesHex}Aborted as the avatar state of the buyer was failed to load."); } sw.Stop(); Log.Verbose("{AddressesHex}Buy Get Buyer AgentAvatarStates: {Elapsed}", addressesHex, sw.Elapsed); sw.Restart(); if (!buyerAvatarState.worldInformation.IsStageCleared(GameConfig.RequireClearedStageLevel.ActionsInShop)) { buyerAvatarState.worldInformation.TryGetLastClearedStageId(out var current); throw new NotEnoughClearedStageLevelException(addressesHex, GameConfig.RequireClearedStageLevel.ActionsInShop, current); } if (!states.TryGetState(ShopState.Address, out Bencodex.Types.Dictionary shopStateDict)) { throw new FailedLoadStateException($"{addressesHex}Aborted as the shop state was failed to load."); } sw.Stop(); Log.Verbose("{AddressesHex}Buy Get ShopState: {Elapsed}", addressesHex, sw.Elapsed); sw.Restart(); Log.Verbose( "{AddressesHex}Execute Buy; buyer: {Buyer} seller: {Seller}", addressesHex, buyerAvatarAddress, sellerAvatarAddress); // 상점에서 구매할 아이템을 찾는다. Dictionary products = (Dictionary)shopStateDict["products"]; IKey productIdSerialized = (IKey)productId.Serialize(); if (!products.ContainsKey(productIdSerialized)) { throw new ItemDoesNotExistException( $"{addressesHex}Aborted as the shop item ({productId}) was failed to get from the shop." ); } ShopItem shopItem = new ShopItem((Dictionary)products[productIdSerialized]); if (!shopItem.SellerAgentAddress.Equals(sellerAgentAddress)) { throw new ItemDoesNotExistException( $"{addressesHex}Aborted as the shop item ({productId}) of seller ({shopItem.SellerAgentAddress}) is different from ({sellerAgentAddress})." ); } sw.Stop(); Log.Verbose("{AddressesHex}Buy Get Item: {Elapsed}", addressesHex, sw.Elapsed); sw.Restart(); if (0 < shopItem.ExpiredBlockIndex && shopItem.ExpiredBlockIndex < context.BlockIndex) { throw new ShopItemExpiredException( $"{addressesHex}Aborted as the shop item ({productId}) already expired on # ({shopItem.ExpiredBlockIndex})."); } if (!states.TryGetAvatarState(sellerAgentAddress, sellerAvatarAddress, out var sellerAvatarState)) { throw new FailedLoadStateException( $"{addressesHex}Aborted as the seller agent/avatar was failed to load from {sellerAgentAddress}/{sellerAvatarAddress}." ); } sw.Stop(); Log.Verbose("{AddressesHex}Buy Get Seller AgentAvatarStates: {Elapsed}", addressesHex, sw.Elapsed); sw.Restart(); // 돈은 있냐? FungibleAssetValue buyerBalance = states.GetBalance(context.Signer, states.GetGoldCurrency()); if (buyerBalance < shopItem.Price) { throw new InsufficientBalanceException( ctx.Signer, buyerBalance, $"{addressesHex}Aborted as the buyer ({ctx.Signer}) has no sufficient gold: {buyerBalance} < {shopItem.Price}" ); } var tax = shopItem.Price.DivRem(100, out _) * TaxRate; var taxedPrice = shopItem.Price - tax; // 세금을 송금한다. states = states.TransferAsset( context.Signer, GoldCurrencyState.Address, tax); // 구매자의 돈을 판매자에게 송금한다. states = states.TransferAsset( context.Signer, sellerAgentAddress, taxedPrice ); products = (Dictionary)products.Remove(productIdSerialized); shopStateDict = shopStateDict.SetItem("products", products); INonFungibleItem nonFungibleItem = (INonFungibleItem)shopItem.ItemUsable ?? shopItem.Costume; if (!sellerAvatarState.inventory.RemoveNonFungibleItem(nonFungibleItem)) { if (nonFungibleItem.RequiredBlockIndex != 0) { throw new ItemDoesNotExistException( $"{addressesHex}Aborted as the {nameof(nonFungibleItem)} ({nonFungibleItem.ItemId}) was failed to get from the sellerAvatar." ); } } nonFungibleItem.Update(context.BlockIndex); // 구매자, 판매자에게 결과 메일 전송 buyerResult = new BuyerResult { shopItem = shopItem, itemUsable = shopItem.ItemUsable, costume = shopItem.Costume }; var buyerMail = new BuyerMail(buyerResult, ctx.BlockIndex, ctx.Random.GenerateRandomGuid(), ctx.BlockIndex); buyerResult.id = buyerMail.id; sellerResult = new SellerResult { shopItem = shopItem, itemUsable = shopItem.ItemUsable, costume = shopItem.Costume, gold = taxedPrice }; var sellerMail = new SellerMail(sellerResult, ctx.BlockIndex, ctx.Random.GenerateRandomGuid(), ctx.BlockIndex); sellerResult.id = sellerMail.id; buyerAvatarState.UpdateV4(buyerMail, context.BlockIndex); if (buyerResult.itemUsable != null) { buyerAvatarState.UpdateFromAddItem(buyerResult.itemUsable, false); } if (buyerResult.costume != null) { buyerAvatarState.UpdateFromAddCostume(buyerResult.costume, false); } sellerAvatarState.UpdateV4(sellerMail, context.BlockIndex); // 퀘스트 업데이트 buyerAvatarState.questList.UpdateTradeQuest(TradeType.Buy, shopItem.Price); sellerAvatarState.questList.UpdateTradeQuest(TradeType.Sell, shopItem.Price); buyerAvatarState.updatedAt = ctx.BlockIndex; buyerAvatarState.blockIndex = ctx.BlockIndex; sellerAvatarState.updatedAt = ctx.BlockIndex; sellerAvatarState.blockIndex = ctx.BlockIndex; var materialSheet = states.GetSheet <MaterialItemSheet>(); buyerAvatarState.UpdateQuestRewards(materialSheet); sellerAvatarState.UpdateQuestRewards(materialSheet); states = states.SetState(sellerAvatarAddress, sellerAvatarState.Serialize()); sw.Stop(); Log.Verbose("{AddressesHex}Buy Set Seller AvatarState: {Elapsed}", addressesHex, sw.Elapsed); sw.Restart(); states = states.SetState(buyerAvatarAddress, buyerAvatarState.Serialize()); sw.Stop(); Log.Verbose("{AddressesHex}Buy Set Buyer AvatarState: {Elapsed}", addressesHex, sw.Elapsed); sw.Restart(); states = states.SetState(ShopState.Address, shopStateDict); sw.Stop(); var ended = DateTimeOffset.UtcNow; Log.Verbose("{AddressesHex}Buy Set ShopState: {Elapsed}", addressesHex, sw.Elapsed); Log.Verbose("{AddressesHex}Buy Total Executed Time: {Elapsed}", addressesHex, ended - started); return(states); }
public override IAccountStateDelta Execute(IActionContext context) { IActionContext ctx = context; var states = ctx.PreviousStates; if (ctx.Rehearsal) { states = states .SetState(buyerAvatarAddress, MarkChanged) .SetState(ctx.Signer, MarkChanged); foreach (var info in purchaseInfos) { var sellerAgentAddress = info.sellerAgentAddress; var sellerAvatarAddress = info.sellerAvatarAddress; states = states.SetState(sellerAvatarAddress, MarkChanged) .MarkBalanceChanged( GoldCurrencyMock, ctx.Signer, sellerAgentAddress, GoldCurrencyState.Address); } return(states.SetState(ShopState.Address, MarkChanged)); } CheckObsolete(BlockChain.Policy.BlockPolicySource.V100080ObsoleteIndex, context); var availableInfos = purchaseInfos.Where(p => !(p is null)); var sellerAgentAddresses = availableInfos.Select(p => p.sellerAgentAddress); var sellerAvatarAddresses = availableInfos.Select(p => p.sellerAvatarAddress); var avatarAddresses = sellerAvatarAddresses.Prepend(buyerAvatarAddress); var addressesHex = GetSignerAndOtherAddressesHex(context, avatarAddresses.ToArray()); if (sellerAgentAddresses.Any(a => a.Equals(ctx.Signer))) { throw new InvalidAddressException($"{addressesHex}Aborted as the signer is the seller."); } var sw = new Stopwatch(); sw.Start(); var started = DateTimeOffset.UtcNow; Log.Verbose("{Addresses}BuyMultiple exec started", addressesHex); if (!states.TryGetAvatarState(ctx.Signer, buyerAvatarAddress, out var buyerAvatarState)) { throw new FailedLoadStateException($"{addressesHex}Aborted as the avatar state of the buyer was failed to load."); } sw.Stop(); Log.Verbose("{AddressesHex}BuyMultiple Get Buyer AgentAvatarStates: {Elapsed}", addressesHex, sw.Elapsed); sw.Restart(); if (!buyerAvatarState.worldInformation.IsStageCleared(GameConfig.RequireClearedStageLevel.ActionsInShop)) { buyerAvatarState.worldInformation.TryGetLastClearedStageId(out var current); throw new NotEnoughClearedStageLevelException( addressesHex, GameConfig.RequireClearedStageLevel.ActionsInShop, current); } if (!states.TryGetState(ShopState.Address, out Bencodex.Types.Dictionary shopStateDict)) { throw new FailedLoadStateException($"{addressesHex}Aborted as the shop state was failed to load."); } sw.Stop(); Log.Verbose("{AddressesHex}BuyMultiple Get ShopState: {Elapsed}", addressesHex, sw.Elapsed); var sellerAvatarAddressesString = string.Join(", ", sellerAvatarAddresses.Select(a => a.ToString())); Log.Verbose( "{AddressesHex}Execute BuyMultiple; buyer: {Buyer} sellers: {Seller}", addressesHex, buyerAvatarAddress, sellerAvatarAddressesString); // Get products in `ShopState`. Dictionary productDict = (Dictionary)shopStateDict["products"]; buyerResult = new BuyerResult(); sellerResult = new SellerResult(); var purchaseResults = new List <PurchaseResult>(); var sellerResults = new List <Buy7.SellerResult>(); var materialSheet = states.GetSheet <MaterialItemSheet>(); foreach (var productInfo in purchaseInfos) { if (productInfo is null) { continue; } var productId = productInfo.productId; var purchaseResult = new PurchaseResult(productId); purchaseResults.Add(purchaseResult); IKey productIdSerialized = (IKey)productId.Serialize(); if (!productDict.ContainsKey(productIdSerialized)) { purchaseResult.errorCode = ERROR_CODE_ITEM_DOES_NOT_EXIST; continue; } ShopItem shopItem = new ShopItem((Dictionary)productDict[productIdSerialized]); purchaseResult.shopItem = shopItem; purchaseResult.itemUsable = shopItem.ItemUsable; purchaseResult.costume = shopItem.Costume; sw.Restart(); var avatarAddress = productInfo.sellerAvatarAddress; if (!states.TryGetAvatarState(productInfo.sellerAgentAddress, avatarAddress, out var sellerAvatarState)) { purchaseResult.errorCode = ERROR_CODE_FAILED_LOADING_STATE; continue; } sw.Stop(); Log.Verbose("{AddressesHex}BuyMultiple Get Seller AgentAvatarState: {Elapsed}", avatarAddress, sw.Elapsed); sw.Restart(); if (!shopItem.SellerAgentAddress.Equals(productInfo.sellerAgentAddress)) { purchaseResult.errorCode = ERROR_CODE_ITEM_DOES_NOT_EXIST; continue; } sw.Stop(); Log.Verbose("{AddressesHex}BuyMultiple Get Item: {Elapsed}", addressesHex, sw.Elapsed); if (0 < shopItem.ExpiredBlockIndex && shopItem.ExpiredBlockIndex < context.BlockIndex) { purchaseResult.errorCode = ERROR_CODE_SHOPITEM_EXPIRED; continue; } // Check buyer's balance FungibleAssetValue buyerBalance = states.GetBalance(context.Signer, states.GetGoldCurrency()); if (buyerBalance < shopItem.Price) { purchaseResult.errorCode = ERROR_CODE_INSUFFICIENT_BALANCE; continue; } var tax = shopItem.Price.DivRem(100, out _) * Buy.TaxRate; var taxedPrice = shopItem.Price - tax; // Transfer tax states = states.TransferAsset( context.Signer, GoldCurrencyState.Address, tax); // Transfer paid money (taxed) to the seller. states = states.TransferAsset( context.Signer, productInfo.sellerAgentAddress, taxedPrice ); productDict = (Dictionary)productDict.Remove(productIdSerialized); shopStateDict = shopStateDict.SetItem("products", productDict); var buyerMail = new BuyerMail(purchaseResult, ctx.BlockIndex, ctx.Random.GenerateRandomGuid(), ctx.BlockIndex); purchaseResult.id = buyerMail.id; var sellerResultToAdd = new Buy7.SellerResult { shopItem = shopItem, itemUsable = shopItem.ItemUsable, costume = shopItem.Costume, gold = taxedPrice }; var sellerMail = new SellerMail(sellerResultToAdd, ctx.BlockIndex, ctx.Random.GenerateRandomGuid(), ctx.BlockIndex); sellerResultToAdd.id = sellerMail.id; sellerResults.Add(sellerResultToAdd); buyerAvatarState.Update(buyerMail); if (purchaseResult.itemUsable != null) { buyerAvatarState.UpdateFromAddItem2(purchaseResult.itemUsable, false); } if (purchaseResult.costume != null) { buyerAvatarState.UpdateFromAddCostume(purchaseResult.costume, false); } sellerAvatarState.Update(sellerMail); // Update quest. buyerAvatarState.questList.UpdateTradeQuest(TradeType.Buy, shopItem.Price); sellerAvatarState.questList.UpdateTradeQuest(TradeType.Sell, shopItem.Price); sellerAvatarState.updatedAt = ctx.BlockIndex; sellerAvatarState.blockIndex = ctx.BlockIndex; sellerAvatarState.UpdateQuestRewards2(materialSheet); sw.Restart(); states = states.SetState(productInfo.sellerAvatarAddress, sellerAvatarState.Serialize()); sw.Stop(); Log.Verbose("{AddressesHex}BuyMultiple Set Seller AvatarState: {Elapsed}", addressesHex, sw.Elapsed); } buyerResult.purchaseResults = purchaseResults; sellerResult.sellerResults = sellerResults; buyerAvatarState.updatedAt = ctx.BlockIndex; buyerAvatarState.blockIndex = ctx.BlockIndex; buyerAvatarState.UpdateQuestRewards2(materialSheet); sw.Restart(); states = states.SetState(buyerAvatarAddress, buyerAvatarState.Serialize()); sw.Stop(); Log.Verbose("{AddressesHex}BuyMultiple Set Buyer AvatarState: {Elapsed}", addressesHex, sw.Elapsed); sw.Restart(); states = states.SetState(ShopState.Address, shopStateDict); sw.Stop(); var ended = DateTimeOffset.UtcNow; Log.Verbose("{AddressesHex}BuyMultiple Set ShopState: {Elapsed}", addressesHex, sw.Elapsed); Log.Verbose("{AddressesHex}BuyMultiple Total Executed Time: {Elapsed}", addressesHex, ended - started); return(states); }
public override IAccountStateDelta Execute(IActionContext context) { IActionContext ctx = context; var states = ctx.PreviousStates; if (ctx.Rehearsal) { foreach (var purchaseInfo in purchaseInfos) { Address shardedShopAddress = ShardedShopState.DeriveAddress(purchaseInfo.itemSubType, purchaseInfo.productId); states = states .SetState(shardedShopAddress, MarkChanged) .SetState(purchaseInfo.sellerAvatarAddress, MarkChanged) .MarkBalanceChanged( GoldCurrencyMock, ctx.Signer, purchaseInfo.sellerAgentAddress, GoldCurrencyState.Address); } return(states .SetState(buyerAvatarAddress, MarkChanged) .SetState(ctx.Signer, MarkChanged) .SetState(Addresses.Shop, MarkChanged)); } CheckObsolete(BlockChain.Policy.BlockPolicySource.V100080ObsoleteIndex, context); var addressesHex = GetSignerAndOtherAddressesHex(context, buyerAvatarAddress); var sw = new Stopwatch(); sw.Start(); var started = DateTimeOffset.UtcNow; Log.Verbose("{AddressesHex}Buy exec started", addressesHex); if (!states.TryGetAvatarState(ctx.Signer, buyerAvatarAddress, out var buyerAvatarState)) { throw new FailedLoadStateException( $"{addressesHex}Aborted as the avatar state of the buyer was failed to load."); } sw.Stop(); Log.Verbose("{AddressesHex}Buy Get Buyer AgentAvatarStates: {Elapsed}", addressesHex, sw.Elapsed); sw.Restart(); if (!buyerAvatarState.worldInformation.IsStageCleared(GameConfig.RequireClearedStageLevel.ActionsInShop)) { buyerAvatarState.worldInformation.TryGetLastClearedStageId(out var current); throw new NotEnoughClearedStageLevelException(addressesHex, GameConfig.RequireClearedStageLevel.ActionsInShop, current); } List <Buy7.PurchaseResult> purchaseResults = new List <Buy7.PurchaseResult>(); List <Buy7.SellerResult> sellerResults = new List <Buy7.SellerResult>(); MaterialItemSheet materialSheet = states.GetSheet <MaterialItemSheet>(); buyerMultipleResult = new Buy7.BuyerMultipleResult(); sellerMultipleResult = new Buy7.SellerMultipleResult(); foreach (var purchaseInfo in purchaseInfos) { Buy7.PurchaseResult purchaseResult = new Buy7.PurchaseResult(purchaseInfo.productId); Address shardedShopAddress = ShardedShopState.DeriveAddress(purchaseInfo.itemSubType, purchaseInfo.productId); Address sellerAgentAddress = purchaseInfo.sellerAgentAddress; Address sellerAvatarAddress = purchaseInfo.sellerAvatarAddress; Guid productId = purchaseInfo.productId; purchaseResults.Add(purchaseResult); if (purchaseInfo.sellerAgentAddress == ctx.Signer) { purchaseResult.errorCode = ErrorCodeInvalidAddress; continue; } if (!states.TryGetState(shardedShopAddress, out Bencodex.Types.Dictionary shopStateDict)) { ShardedShopState shardedShopState = new ShardedShopState(shardedShopAddress); shopStateDict = (Dictionary)shardedShopState.Serialize(); } sw.Stop(); Log.Verbose("{AddressesHex}Buy Get ShopState: {Elapsed}", addressesHex, sw.Elapsed); sw.Restart(); Log.Verbose( "{AddressesHex}Execute Buy; buyer: {Buyer} seller: {Seller}", addressesHex, buyerAvatarAddress, sellerAvatarAddress); // Find product from ShardedShopState. List products = (List)shopStateDict[ProductsKey]; IValue productIdSerialized = productId.Serialize(); Dictionary productSerialized = products .Select(p => (Dictionary)p) .FirstOrDefault(p => p[LegacyProductIdKey].Equals(productIdSerialized)); // Since Bencodex 0.4, Dictionary/List are reference types; so their default values // are not a empty container, but a null reference: productSerialized = productSerialized ?? Dictionary.Empty; bool fromLegacy = false; if (productSerialized.Equals(Dictionary.Empty)) { // Backward compatibility. IValue rawShop = states.GetState(Addresses.Shop); if (!(rawShop is null)) { Dictionary legacyShopDict = (Dictionary)rawShop; Dictionary legacyProducts = (Dictionary)legacyShopDict[LegacyProductsKey]; IKey productKey = (IKey)productId.Serialize(); // SoldOut if (!legacyProducts.ContainsKey(productKey)) { purchaseResult.errorCode = ErrorCodeItemDoesNotExist; continue; } productSerialized = (Dictionary)legacyProducts[productKey]; legacyProducts = (Dictionary)legacyProducts.Remove(productKey); legacyShopDict = legacyShopDict.SetItem(LegacyProductsKey, legacyProducts); states = states.SetState(Addresses.Shop, legacyShopDict); fromLegacy = true; } } ShopItem shopItem = new ShopItem(productSerialized); if (!shopItem.SellerAgentAddress.Equals(sellerAgentAddress)) { purchaseResult.errorCode = ErrorCodeItemDoesNotExist; continue; } sw.Stop(); Log.Verbose("{AddressesHex}Buy Get Item: {Elapsed}", addressesHex, sw.Elapsed); sw.Restart(); if (0 < shopItem.ExpiredBlockIndex && shopItem.ExpiredBlockIndex < context.BlockIndex) { purchaseResult.errorCode = ErrorCodeShopItemExpired; continue; } if (!states.TryGetAvatarState(sellerAgentAddress, sellerAvatarAddress, out var sellerAvatarState)) { purchaseResult.errorCode = ErrorCodeFailedLoadingState; continue; } sw.Stop(); Log.Verbose("{AddressesHex}Buy Get Seller AgentAvatarStates: {Elapsed}", addressesHex, sw.Elapsed); sw.Restart(); // Check Balance. FungibleAssetValue buyerBalance = states.GetBalance(context.Signer, states.GetGoldCurrency()); if (buyerBalance < shopItem.Price) { purchaseResult.errorCode = ErrorCodeInsufficientBalance; continue; } var tax = shopItem.Price.DivRem(100, out _) * TaxRate; var taxedPrice = shopItem.Price - tax; // Transfer tax. states = states.TransferAsset( context.Signer, GoldCurrencyState.Address, tax); // Transfer seller. states = states.TransferAsset( context.Signer, sellerAgentAddress, taxedPrice ); products = (List)products.Remove(productSerialized); shopStateDict = shopStateDict.SetItem(ProductsKey, new List <IValue>(products)); INonFungibleItem nonFungibleItem = (INonFungibleItem)shopItem.ItemUsable ?? shopItem.Costume; if (!sellerAvatarState.inventory.RemoveNonFungibleItem(nonFungibleItem) && !fromLegacy) { purchaseResult.errorCode = ErrorCodeItemDoesNotExist; continue; } nonFungibleItem.RequiredBlockIndex = context.BlockIndex; // Send result mail for buyer, seller. purchaseResult.shopItem = shopItem; purchaseResult.itemUsable = shopItem.ItemUsable; purchaseResult.costume = shopItem.Costume; var buyerMail = new BuyerMail(purchaseResult, ctx.BlockIndex, ctx.Random.GenerateRandomGuid(), ctx.BlockIndex); purchaseResult.id = buyerMail.id; var sellerResult = new Buy7.SellerResult { shopItem = shopItem, itemUsable = shopItem.ItemUsable, costume = shopItem.Costume, gold = taxedPrice }; var sellerMail = new SellerMail(sellerResult, ctx.BlockIndex, ctx.Random.GenerateRandomGuid(), ctx.BlockIndex); sellerResult.id = sellerMail.id; sellerResults.Add(sellerResult); buyerAvatarState.Update(buyerMail); if (purchaseResult.itemUsable != null) { buyerAvatarState.UpdateFromAddItem2(purchaseResult.itemUsable, false); } if (purchaseResult.costume != null) { buyerAvatarState.UpdateFromAddCostume(purchaseResult.costume, false); } sellerAvatarState.Update(sellerMail); // Update quest. buyerAvatarState.questList.UpdateTradeQuest(TradeType.Buy, shopItem.Price); sellerAvatarState.questList.UpdateTradeQuest(TradeType.Sell, shopItem.Price); sellerAvatarState.updatedAt = ctx.BlockIndex; sellerAvatarState.blockIndex = ctx.BlockIndex; buyerAvatarState.UpdateQuestRewards2(materialSheet); sellerAvatarState.UpdateQuestRewards2(materialSheet); states = states.SetState(sellerAvatarAddress, sellerAvatarState.Serialize()); sw.Stop(); Log.Verbose("{AddressesHex}Buy Set Seller AvatarState: {Elapsed}", addressesHex, sw.Elapsed); sw.Restart(); states = states.SetState(shardedShopAddress, shopStateDict); sw.Stop(); Log.Verbose("{AddressesHex}Buy Set ShopState: {Elapsed}", addressesHex, sw.Elapsed); } buyerMultipleResult.purchaseResults = purchaseResults; sellerMultipleResult.sellerResults = sellerResults; buyerAvatarState.updatedAt = ctx.BlockIndex; buyerAvatarState.blockIndex = ctx.BlockIndex; states = states.SetState(buyerAvatarAddress, buyerAvatarState.Serialize()); sw.Stop(); Log.Verbose("{AddressesHex}Buy Set Buyer AvatarState: {Elapsed}", addressesHex, sw.Elapsed); sw.Restart(); var ended = DateTimeOffset.UtcNow; Log.Verbose("{AddressesHex}Buy Total Executed Time: {Elapsed}", addressesHex, ended - started); return(states); }
public override IAccountStateDelta Execute(IActionContext context) { IActionContext ctx = context; var states = ctx.PreviousStates; if (ctx.Rehearsal) { states = states .SetState(buyerAvatarAddress, MarkChanged) .SetState(ctx.Signer, MarkChanged) .SetState(sellerAvatarAddress, MarkChanged) .MarkBalanceChanged(GoldCurrencyMock, ctx.Signer, sellerAgentAddress); return(states.SetState(ShopState.Address, MarkChanged)); } if (ctx.Signer.Equals(sellerAgentAddress)) { return(LogError(context, "Aborted as the signer is the seller.")); } var sw = new Stopwatch(); sw.Start(); var started = DateTimeOffset.UtcNow; Log.Debug("Buy exec started."); if (!states.TryGetAgentAvatarStates(ctx.Signer, buyerAvatarAddress, out var buyerAgentState, out var buyerAvatarState)) { return(LogError(context, "Aborted as the avatar state of the buyer was failed to load.")); } sw.Stop(); Log.Debug("Buy Get Buyer AgentAvatarStates: {Elapsed}", sw.Elapsed); sw.Restart(); if (!buyerAvatarState.worldInformation.TryGetUnlockedWorldByStageClearedBlockIndex(out var world)) { return(LogError(context, "Aborted as the WorldInformation was failed to load.")); } if (world.StageClearedId < GameConfig.RequireClearedStageLevel.ActionsInShop) { // 스테이지 클리어 부족 에러. return(LogError( context, "Aborted as the signer is not cleared the minimum stage level required to use shop yet: {ClearedLevel} < {RequiredLevel}.", world.StageClearedId, GameConfig.RequireClearedStageLevel.ActionsInShop )); } if (!states.TryGetState(ShopState.Address, out Bencodex.Types.Dictionary d)) { return(LogError(context, "Aborted as the shop state was failed to load.")); } var shopState = new ShopState(d); sw.Stop(); Log.Debug("Buy Get ShopState: {Elapsed}", sw.Elapsed); sw.Restart(); Log.Debug("Execute Buy; buyer: {Buyer} seller: {Seller}", buyerAvatarAddress, sellerAvatarAddress); // 상점에서 구매할 아이템을 찾는다. if (!shopState.TryGet(sellerAgentAddress, productId, out var outPair)) { return(LogError( context, "Aborted as the shop item ({ProductId}) was failed to get from the seller agent ({SellerAgent}).", productId, sellerAgentAddress )); } sw.Stop(); Log.Debug($"Buy Get Item: {sw.Elapsed}"); sw.Restart(); if (!states.TryGetAgentAvatarStates(sellerAgentAddress, sellerAvatarAddress, out var sellerAgentState, out var sellerAvatarState)) { return(LogError( context, "Aborted as the seller agent/avatar was filed to load from {SellerAgent}/{SellerAvatar}.", sellerAgentAddress, sellerAvatarAddress )); } sw.Stop(); Log.Debug($"Buy Get Seller AgentAvatarStates: {sw.Elapsed}"); sw.Restart(); // 돈은 있냐? BigInteger buyerBalance = states.GetBalance(context.Signer, states.GetGoldCurrency()); if (buyerBalance < outPair.Value.Price) { return(LogError( context, "Aborted as the buyer ({Buyer}) has no sufficient gold: {BuyerBalance} < {ItemPrice}", ctx.Signer, buyerBalance, outPair.Value.Price )); } var taxedPrice = (BigInteger)decimal.Round((decimal)outPair.Value.Price * 0.92m); // 구매자의 돈을 판매자에게 송금한다. states = states.TransferAsset(context.Signer, sellerAgentAddress, states.GetGoldCurrency(), outPair.Value.Price); // 상점에서 구매할 아이템을 제거한다. if (!shopState.Unregister(sellerAgentAddress, outPair.Value)) { return(LogError( context, "Aborted as the item ({@ProductId}) was failed to unregister from seller ({Seller})'s inventory.", outPair.Value.ProductId, sellerAgentAddress )); } // 구매자, 판매자에게 결과 메일 전송 buyerResult = new BuyerResult { shopItem = outPair.Value, itemUsable = outPair.Value.ItemUsable }; var buyerMail = new BuyerMail(buyerResult, ctx.BlockIndex, ctx.Random.GenerateRandomGuid(), ctx.BlockIndex); buyerResult.id = buyerMail.id; sellerResult = new SellerResult { shopItem = outPair.Value, itemUsable = outPair.Value.ItemUsable, gold = taxedPrice }; var sellerMail = new SellerMail(sellerResult, ctx.BlockIndex, ctx.Random.GenerateRandomGuid(), ctx.BlockIndex); sellerResult.id = sellerMail.id; buyerAvatarState.Update(buyerMail); buyerAvatarState.UpdateFromAddItem(buyerResult.itemUsable, false); sellerAvatarState.Update(sellerMail); // 퀘스트 업데이트 buyerAvatarState.questList.UpdateTradeQuest(TradeType.Buy, outPair.Value.Price); sellerAvatarState.questList.UpdateTradeQuest(TradeType.Sell, outPair.Value.Price); var timestamp = DateTimeOffset.UtcNow; buyerAvatarState.updatedAt = timestamp; buyerAvatarState.blockIndex = ctx.BlockIndex; sellerAvatarState.updatedAt = timestamp; sellerAvatarState.blockIndex = ctx.BlockIndex; buyerAvatarState.UpdateQuestRewards(ctx); sellerAvatarState.UpdateQuestRewards(ctx); states = states.SetState(sellerAvatarAddress, sellerAvatarState.Serialize()); sw.Stop(); Log.Debug("Buy Set Seller AvatarState: {Elapsed}", sw.Elapsed); sw.Restart(); states = states.SetState(buyerAvatarAddress, buyerAvatarState.Serialize()); sw.Stop(); Log.Debug("Buy Set Buyer AvatarState: {Elapsed}", sw.Elapsed); sw.Restart(); states = states.SetState(ShopState.Address, shopState.Serialize()); sw.Stop(); var ended = DateTimeOffset.UtcNow; Log.Debug("Buy Set ShopState: {Elapsed}", sw.Elapsed); Log.Debug("Buy Total Executed Time: {Elapsed}", ended - started); return(states.SetState(ctx.Signer, buyerAgentState.Serialize())); }