public override IAccountStateDelta Execute(IActionContext context) { IActionContext ctx = context; var states = ctx.PreviousStates; var slotAddress = AvatarAddress.Derive( string.Format( CultureInfo.InvariantCulture, CombinationSlotState.DeriveFormat, slotIndex ) ); if (ctx.Rehearsal) { return(states .SetState(AvatarAddress, MarkChanged) .SetState(ctx.Signer, MarkChanged) .SetState(slotAddress, MarkChanged)); } CheckObsolete(BlockChain.Policy.BlockPolicySource.V100080ObsoleteIndex, context); var addressesHex = GetSignerAndOtherAddressesHex(context, AvatarAddress); var sw = new Stopwatch(); sw.Start(); var started = DateTimeOffset.UtcNow; Log.Verbose("{AddressesHex}Combination exec started", addressesHex); if (!states.TryGetAvatarState(ctx.Signer, AvatarAddress, out AvatarState avatarState)) { throw new FailedLoadStateException($"{addressesHex}Aborted as the avatar state of the signer was failed to load."); } sw.Stop(); Log.Verbose("{AddressesHex}Combination Get AgentAvatarStates: {Elapsed}", addressesHex, sw.Elapsed); sw.Restart(); if (!avatarState.worldInformation.IsStageCleared(GameConfig.RequireClearedStageLevel.CombinationEquipmentAction)) { avatarState.worldInformation.TryGetLastClearedStageId(out var current); throw new NotEnoughClearedStageLevelException( addressesHex, GameConfig.RequireClearedStageLevel.CombinationEquipmentAction, current); } var slotState = states.GetCombinationSlotState(AvatarAddress, slotIndex); if (slotState is null) { throw new FailedLoadStateException($"{addressesHex}Aborted as the slot state is failed to load: # {slotIndex}"); } if (!slotState.Validate(avatarState, ctx.BlockIndex)) { throw new CombinationSlotUnlockException( $"{addressesHex}Aborted as the slot state is invalid: {slotState} @ {slotIndex}"); } Log.Verbose("{AddressesHex}Execute Combination; player: {Player}", addressesHex, AvatarAddress); var consumableItemSheet = states.GetSheet <ConsumableItemSheet>(); var recipeRow = states.GetSheet <ConsumableItemRecipeSheet>().Values.FirstOrDefault(r => r.Id == recipeId); if (recipeRow is null) { throw new SheetRowNotFoundException(addressesHex, nameof(ConsumableItemRecipeSheet), recipeId); } var materials = new Dictionary <Material, int>(); foreach (var materialInfo in recipeRow.Materials.OrderBy(r => r.Id)) { var materialId = materialInfo.Id; var count = materialInfo.Count; if (avatarState.inventory.HasItem(materialId, count)) { avatarState.inventory.TryGetItem(materialId, out var inventoryItem); if (!(inventoryItem.item is Material material)) { throw new InvalidMaterialException($"Aborted because material id({materialId}) not valid"); } materials[material] = count; avatarState.inventory.RemoveFungibleItem2(material, count); } else { throw new NotEnoughMaterialException( $"{addressesHex}Aborted as the player has no enough material ({materialId} * {count})"); } } sw.Stop(); Log.Verbose("{AddressesHex}Combination Remove Materials: {Elapsed}", addressesHex, sw.Elapsed); sw.Restart(); var result = new CombinationConsumable5.ResultModel { materials = materials, itemType = ItemType.Consumable, }; var costAP = recipeRow.RequiredActionPoint; if (avatarState.actionPoint < costAP) { throw new NotEnoughActionPointException( $"{addressesHex}Aborted due to insufficient action point: {avatarState.actionPoint} < {costAP}" ); } // ap 차감. avatarState.actionPoint -= costAP; result.actionPoint = costAP; var resultConsumableItemId = recipeRow.ResultConsumableItemId; sw.Stop(); Log.Verbose("{AddressesHex}Combination Get Food id: {Elapsed}", addressesHex, sw.Elapsed); sw.Restart(); result.recipeId = recipeRow.Id; if (!consumableItemSheet.TryGetValue(resultConsumableItemId, out var consumableItemRow)) { throw new SheetRowNotFoundException(addressesHex, nameof(ConsumableItemSheet), resultConsumableItemId); } // 조합 결과 획득. var requiredBlockIndex = ctx.BlockIndex + recipeRow.RequiredBlockIndex; var itemId = ctx.Random.GenerateRandomGuid(); var itemUsable = GetFood(consumableItemRow, itemId, requiredBlockIndex); // 액션 결과 result.itemUsable = itemUsable; var mail = new CombinationMail( result, ctx.BlockIndex, ctx.Random.GenerateRandomGuid(), requiredBlockIndex ); result.id = mail.id; avatarState.Update(mail); avatarState.UpdateFromCombination2(itemUsable); sw.Stop(); Log.Verbose("{AddressesHex}Combination Update AvatarState: {Elapsed}", addressesHex, sw.Elapsed); sw.Restart(); var materialSheet = states.GetSheet <MaterialItemSheet>(); avatarState.UpdateQuestRewards2(materialSheet); avatarState.updatedAt = ctx.BlockIndex; avatarState.blockIndex = ctx.BlockIndex; states = states.SetState(AvatarAddress, avatarState.Serialize()); slotState.Update(result, ctx.BlockIndex, requiredBlockIndex); sw.Stop(); Log.Verbose("{AddressesHex}Combination Set AvatarState: {Elapsed}", addressesHex, sw.Elapsed); var ended = DateTimeOffset.UtcNow; Log.Verbose("{AddressesHex}Combination Total Executed Time: {Elapsed}", addressesHex, ended - started); return(states .SetState(slotAddress, slotState.Serialize())); }
public override IAccountStateDelta Execute(IActionContext context) { IActionContext ctx = context; var states = ctx.PreviousStates; var slotAddress = AvatarAddress.Derive( string.Format( CultureInfo.InvariantCulture, CombinationSlotState.DeriveFormat, SlotIndex ) ); if (ctx.Rehearsal) { return(states .SetState(AvatarAddress, MarkChanged) .SetState(slotAddress, MarkChanged) .SetState(ctx.Signer, MarkChanged) .MarkBalanceChanged(GoldCurrencyMock, ctx.Signer, BlacksmithAddress)); } CheckObsolete(BlockChain.Policy.BlockPolicySource.V100080ObsoleteIndex, context); var addressesHex = GetSignerAndOtherAddressesHex(context, AvatarAddress); if (!states.TryGetAgentAvatarStates(ctx.Signer, AvatarAddress, out var agentState, out var avatarState)) { throw new FailedLoadStateException($"{addressesHex}Aborted as the avatar state of the signer was failed to load."); } var slotState = states.GetCombinationSlotState(AvatarAddress, SlotIndex); if (slotState is null) { throw new FailedLoadStateException($"{addressesHex}Aborted as the slot state is failed to load"); } if (!slotState.Validate(avatarState, ctx.BlockIndex)) { throw new CombinationSlotUnlockException( $"{addressesHex}Aborted as the slot state is invalid: {slotState} @ {SlotIndex}"); } var recipeSheet = states.GetSheet <EquipmentItemRecipeSheet>(); var materialSheet = states.GetSheet <MaterialItemSheet>(); var materials = new Dictionary <Material, int>(); // Validate recipe. if (!recipeSheet.TryGetValue(RecipeId, out var recipe)) { throw new SheetRowNotFoundException(addressesHex, nameof(EquipmentItemRecipeSheet), RecipeId); } if (!(SubRecipeId is null)) { if (!recipe.SubRecipeIds.Contains((int)SubRecipeId)) { throw new SheetRowColumnException( $"{addressesHex}Aborted as the sub recipe {SubRecipeId} was failed to load from the sheet." ); } } // Validate main recipe is unlocked. if (!avatarState.worldInformation.IsStageCleared(recipe.UnlockStage)) { avatarState.worldInformation.TryGetLastClearedStageId(out var current); throw new NotEnoughClearedStageLevelException(addressesHex, recipe.UnlockStage, current); } if (!materialSheet.TryGetValue(recipe.MaterialId, out var material)) { throw new SheetRowNotFoundException(addressesHex, nameof(MaterialItemSheet), recipe.MaterialId); } if (!avatarState.inventory.RemoveFungibleItem2(material.ItemId, recipe.MaterialCount)) { throw new NotEnoughMaterialException( $"{addressesHex}Aborted as the player has no enough material ({material} * {recipe.MaterialCount})" ); } var equipmentMaterial = ItemFactory.CreateMaterial(materialSheet, material.Id); materials[equipmentMaterial] = recipe.MaterialCount; BigInteger requiredGold = recipe.RequiredGold; var requiredActionPoint = recipe.RequiredActionPoint; var equipmentItemSheet = states.GetSheet <EquipmentItemSheet>(); // Validate equipment id. if (!equipmentItemSheet.TryGetValue(recipe.ResultEquipmentId, out var equipRow)) { throw new SheetRowNotFoundException(addressesHex, nameof(equipmentItemSheet), recipe.ResultEquipmentId); } var requiredBlockIndex = ctx.BlockIndex + recipe.RequiredBlockIndex; var equipment = (Equipment)ItemFactory.CreateItemUsable( equipRow, ctx.Random.GenerateRandomGuid(), requiredBlockIndex ); // Validate sub recipe. HashSet <int> optionIds = null; if (SubRecipeId.HasValue) { var subSheet = states.GetSheet <EquipmentItemSubRecipeSheet>(); var subId = (int)SubRecipeId; if (!subSheet.TryGetValue(subId, out var subRecipe)) { throw new SheetRowNotFoundException(addressesHex, nameof(EquipmentItemSubRecipeSheet), subId); } requiredBlockIndex += subRecipe.RequiredBlockIndex; requiredGold += subRecipe.RequiredGold; requiredActionPoint += subRecipe.RequiredActionPoint; foreach (var materialInfo in subRecipe.Materials) { if (!materialSheet.TryGetValue(materialInfo.Id, out var subMaterialRow)) { throw new SheetRowNotFoundException(addressesHex, nameof(MaterialItemSheet), materialInfo.Id); } if (!avatarState.inventory.RemoveFungibleItem2(subMaterialRow.ItemId, materialInfo.Count)) { throw new NotEnoughMaterialException( $"{addressesHex}Aborted as the player has no enough material ({subMaterialRow} * {materialInfo.Count})" ); } var subMaterial = ItemFactory.CreateMaterial(materialSheet, materialInfo.Id); materials[subMaterial] = materialInfo.Count; } optionIds = SelectOption(states.GetSheet <EquipmentItemOptionSheet>(), states.GetSheet <SkillSheet>(), subRecipe, ctx.Random, equipment); equipment.Update(requiredBlockIndex); } // Validate NCG. FungibleAssetValue agentBalance = states.GetBalance(ctx.Signer, states.GetGoldCurrency()); if (agentBalance < states.GetGoldCurrency() * requiredGold) { throw new InsufficientBalanceException( ctx.Signer, agentBalance, $"{addressesHex}Aborted as the agent ({ctx.Signer}) has no sufficient gold: {agentBalance} < {requiredGold}" ); } if (avatarState.actionPoint < requiredActionPoint) { throw new NotEnoughActionPointException( $"{addressesHex}Aborted due to insufficient action point: {avatarState.actionPoint} < {requiredActionPoint}" ); } avatarState.actionPoint -= requiredActionPoint; if (!(optionIds is null)) { foreach (var id in optionIds.OrderBy(id => id)) { agentState.unlockedOptions.Add(id); } } // FIXME: BlacksmithAddress just accumulate NCG. we need plan how to circulate this. if (requiredGold > 0) { states = states.TransferAsset( ctx.Signer, BlacksmithAddress, states.GetGoldCurrency() * requiredGold ); } var result = new CombinationConsumable5.ResultModel { actionPoint = requiredActionPoint, gold = requiredGold, materials = materials, itemUsable = equipment, recipeId = RecipeId, subRecipeId = SubRecipeId, itemType = ItemType.Equipment, }; slotState.Update(result, ctx.BlockIndex, requiredBlockIndex); var mail = new CombinationMail(result, ctx.BlockIndex, ctx.Random.GenerateRandomGuid(), requiredBlockIndex); result.id = mail.id; avatarState.Update(mail); avatarState.questList.UpdateCombinationEquipmentQuest(RecipeId); avatarState.UpdateFromCombination2(equipment); avatarState.UpdateQuestRewards2(materialSheet); return(states .SetState(AvatarAddress, avatarState.Serialize()) .SetState(slotAddress, slotState.Serialize()) .SetState(ctx.Signer, agentState.Serialize())); }
public override IAccountStateDelta Execute(IActionContext context) { var states = context.PreviousStates; var slotAddress = avatarAddress.Derive( string.Format( CultureInfo.InvariantCulture, CombinationSlotState.DeriveFormat, slotIndex ) ); var inventoryAddress = avatarAddress.Derive(LegacyInventoryKey); var worldInformationAddress = avatarAddress.Derive(LegacyWorldInformationKey); var questListAddress = avatarAddress.Derive(LegacyQuestListKey); if (context.Rehearsal) { return(states .SetState(avatarAddress, MarkChanged) .SetState(slotAddress, MarkChanged) .SetState(context.Signer, MarkChanged) .SetState(inventoryAddress, MarkChanged) .SetState(worldInformationAddress, MarkChanged) .SetState(questListAddress, MarkChanged) .MarkBalanceChanged(GoldCurrencyMock, context.Signer, BlacksmithAddress)); } var addressesHex = GetSignerAndOtherAddressesHex(context, avatarAddress); if (!states.TryGetAgentAvatarStatesV2(context.Signer, avatarAddress, out var agentState, out var avatarState)) { throw new FailedLoadStateException( $"{addressesHex}Aborted as the avatar state of the signer was failed to load."); } // Validate Required Cleared Stage if (!avatarState.worldInformation.IsStageCleared( GameConfig.RequireClearedStageLevel.CombinationEquipmentAction)) { avatarState.worldInformation.TryGetLastClearedStageId(out var current); throw new NotEnoughClearedStageLevelException( addressesHex, GameConfig.RequireClearedStageLevel.CombinationEquipmentAction, current); } // ~Validate Required Cleared Stage // Validate SlotIndex var slotState = states.GetCombinationSlotState(avatarAddress, slotIndex); if (slotState is null) { throw new FailedLoadStateException( $"{addressesHex}Aborted as the slot state is failed to load: # {slotIndex}"); } if (!slotState.Validate(avatarState, context.BlockIndex)) { throw new CombinationSlotUnlockException( $"{addressesHex}Aborted as the slot state is invalid: {slotState} @ {slotIndex}"); } // ~Validate SlotIndex // Validate Work var costActionPoint = 0; var costNCG = 0L; var endBlockIndex = context.BlockIndex; var requiredFungibleItems = new Dictionary <int, int>(); // Validate RecipeId var equipmentItemRecipeSheet = states.GetSheet <EquipmentItemRecipeSheet>(); if (!equipmentItemRecipeSheet.TryGetValue(recipeId, out var recipeRow)) { throw new SheetRowNotFoundException( addressesHex, nameof(EquipmentItemRecipeSheet), recipeId); } // ~Validate RecipeId // Validate Recipe Unlocked. if (!avatarState.worldInformation.IsStageCleared(recipeRow.UnlockStage)) { avatarState.worldInformation.TryGetLastClearedStageId(out var current); throw new NotEnoughClearedStageLevelException( addressesHex, recipeRow.UnlockStage, current); } // ~Validate Recipe Unlocked // Validate Recipe ResultEquipmentId var equipmentItemSheet = states.GetSheet <EquipmentItemSheet>(); if (!equipmentItemSheet.TryGetValue(recipeRow.ResultEquipmentId, out var equipmentRow)) { throw new SheetRowNotFoundException( addressesHex, nameof(equipmentItemSheet), recipeRow.ResultEquipmentId); } // ~Validate Recipe ResultEquipmentId // Validate Recipe Material var materialItemSheet = states.GetSheet <MaterialItemSheet>(); if (!materialItemSheet.TryGetValue(recipeRow.MaterialId, out var materialRow)) { throw new SheetRowNotFoundException( addressesHex, nameof(MaterialItemSheet), recipeRow.MaterialId); } if (requiredFungibleItems.ContainsKey(materialRow.Id)) { requiredFungibleItems[materialRow.Id] += recipeRow.MaterialCount; } else { requiredFungibleItems[materialRow.Id] = recipeRow.MaterialCount; } // ~Validate Recipe Material // Validate SubRecipeId EquipmentItemSubRecipeSheetV2.Row subRecipeRow = null; if (subRecipeId.HasValue) { if (!recipeRow.SubRecipeIds.Contains(subRecipeId.Value)) { throw new SheetRowColumnException( $"{addressesHex}Aborted as the sub recipe {subRecipeId.Value} was failed to load from the sheet." ); } var equipmentItemSubRecipeSheetV2 = states.GetSheet <EquipmentItemSubRecipeSheetV2>(); if (!equipmentItemSubRecipeSheetV2.TryGetValue(subRecipeId.Value, out subRecipeRow)) { throw new SheetRowNotFoundException( addressesHex, nameof(EquipmentItemSubRecipeSheetV2), subRecipeId.Value); } // Validate SubRecipe Material for (var i = subRecipeRow.Materials.Count; i > 0; i--) { var materialInfo = subRecipeRow.Materials[i - 1]; if (!materialItemSheet.TryGetValue(materialInfo.Id, out materialRow)) { throw new SheetRowNotFoundException( addressesHex, nameof(MaterialItemSheet), materialInfo.Id); } if (requiredFungibleItems.ContainsKey(materialRow.Id)) { requiredFungibleItems[materialRow.Id] += materialInfo.Count; } else { requiredFungibleItems[materialRow.Id] = materialInfo.Count; } } // ~Validate SubRecipe Material costActionPoint += subRecipeRow.RequiredActionPoint; costNCG += subRecipeRow.RequiredGold; endBlockIndex += subRecipeRow.RequiredBlockIndex; } // ~Validate SubRecipeId costActionPoint += recipeRow.RequiredActionPoint; costNCG += recipeRow.RequiredGold; endBlockIndex += recipeRow.RequiredBlockIndex; // ~Validate Work // Remove Required Materials var inventory = avatarState.inventory; foreach (var pair in requiredFungibleItems.OrderBy(pair => pair.Key)) { if (!materialItemSheet.TryGetValue(pair.Key, out materialRow) || !inventory.RemoveFungibleItem(materialRow.ItemId, context.BlockIndex, pair.Value)) { throw new NotEnoughMaterialException( $"{addressesHex}Aborted as the player has no enough material ({pair.Key} * {pair.Value})"); } } // ~Remove Required Materials // Subtract Required ActionPoint if (costActionPoint > 0) { if (avatarState.actionPoint < costActionPoint) { throw new NotEnoughActionPointException( $"{addressesHex}Aborted due to insufficient action point: {avatarState.actionPoint} < {costActionPoint}" ); } avatarState.actionPoint -= costActionPoint; } // ~Subtract Required ActionPoint // Transfer Required NCG if (costNCG > 0L) { states = states.TransferAsset( context.Signer, BlacksmithAddress, states.GetGoldCurrency() * costNCG ); } // ~Transfer Required NCG // Create Equipment var equipment = (Equipment)ItemFactory.CreateItemUsable( equipmentRow, context.Random.GenerateRandomGuid(), endBlockIndex); if (!(subRecipeRow is null)) { AddAndUnlockOption( agentState, equipment, context.Random, subRecipeRow, states.GetSheet <EquipmentItemOptionSheet>(), states.GetSheet <SkillSheet>() ); endBlockIndex = equipment.RequiredBlockIndex; } // ~Create Equipment // Add or Update Equipment avatarState.blockIndex = context.BlockIndex; avatarState.updatedAt = context.BlockIndex; avatarState.questList.UpdateCombinationEquipmentQuest(recipeId); avatarState.UpdateFromCombination(equipment); avatarState.UpdateQuestRewards(materialItemSheet); // ~Add or Update Equipment // Update Slot var mailId = context.Random.GenerateRandomGuid(); var attachmentResult = new CombinationConsumable5.ResultModel { id = mailId, actionPoint = costActionPoint, gold = costNCG, materials = requiredFungibleItems.ToDictionary( e => ItemFactory.CreateMaterial(materialItemSheet, e.Key), e => e.Value), itemUsable = equipment, recipeId = recipeId, subRecipeId = subRecipeId, }; slotState.Update(attachmentResult, context.BlockIndex, endBlockIndex); // ~Update Slot // Create Mail var mail = new CombinationMail( attachmentResult, context.BlockIndex, mailId, endBlockIndex); avatarState.Update(mail); // ~Create Mail return(states .SetState(avatarAddress, avatarState.SerializeV2()) .SetState(inventoryAddress, avatarState.inventory.Serialize()) .SetState(worldInformationAddress, avatarState.worldInformation.Serialize()) .SetState(questListAddress, avatarState.questList.Serialize()) .SetState(slotAddress, slotState.Serialize()) .SetState(context.Signer, agentState.Serialize())); }
public override IAccountStateDelta Execute(IActionContext context) { var states = context.PreviousStates; var slotAddress = avatarAddress.Derive( string.Format( CultureInfo.InvariantCulture, CombinationSlotState.DeriveFormat, slotIndex ) ); var inventoryAddress = avatarAddress.Derive(LegacyInventoryKey); var worldInformationAddress = avatarAddress.Derive(LegacyWorldInformationKey); var questListAddress = avatarAddress.Derive(LegacyQuestListKey); if (context.Rehearsal) { return(states .SetState(avatarAddress, MarkChanged) .SetState(context.Signer, MarkChanged) .SetState(inventoryAddress, MarkChanged) .SetState(worldInformationAddress, MarkChanged) .SetState(questListAddress, MarkChanged) .SetState(slotAddress, MarkChanged)); } var addressesHex = GetSignerAndOtherAddressesHex(context, avatarAddress); if (!states.TryGetAvatarStateV2(context.Signer, avatarAddress, out var avatarState, out _)) { throw new FailedLoadStateException( $"{addressesHex}Aborted as the avatar state of the signer was failed to load."); } // Validate Required Cleared Stage if (!avatarState.worldInformation.IsStageCleared( GameConfig.RequireClearedStageLevel.CombinationConsumableAction)) { avatarState.worldInformation.TryGetLastClearedStageId(out var current); throw new NotEnoughClearedStageLevelException( addressesHex, GameConfig.RequireClearedStageLevel.CombinationConsumableAction, current); } // ~Validate Required Cleared Stage // Validate SlotIndex var slotState = states.GetCombinationSlotState(avatarAddress, slotIndex); if (slotState is null) { throw new FailedLoadStateException( $"{addressesHex}Aborted as the slot state is failed to load: # {slotIndex}"); } if (!slotState.Validate(avatarState, context.BlockIndex)) { throw new CombinationSlotUnlockException( $"{addressesHex}Aborted as the slot state is invalid: {slotState} @ {slotIndex}"); } // ~Validate SlotIndex // Validate Work var costActionPoint = 0; var endBlockIndex = context.BlockIndex; var requiredFungibleItems = new Dictionary <int, int>(); // Validate RecipeId var consumableItemRecipeSheet = states.GetSheet <ConsumableItemRecipeSheet>(); if (!consumableItemRecipeSheet.TryGetValue(recipeId, out var recipeRow)) { throw new SheetRowNotFoundException( addressesHex, nameof(ConsumableItemRecipeSheet), recipeId); } // ~Validate RecipeId // Validate Recipe ResultEquipmentId var consumableItemSheet = states.GetSheet <ConsumableItemSheet>(); if (!consumableItemSheet.TryGetValue(recipeRow.ResultConsumableItemId, out var consumableRow)) { throw new SheetRowNotFoundException( addressesHex, nameof(consumableItemSheet), recipeRow.ResultConsumableItemId); } // ~Validate Recipe ResultEquipmentId // Validate Recipe Material var materialItemSheet = states.GetSheet <MaterialItemSheet>(); for (var i = recipeRow.Materials.Count; i > 0; i--) { var materialInfo = recipeRow.Materials[i - 1]; if (!materialItemSheet.TryGetValue(materialInfo.Id, out var materialRow)) { throw new SheetRowNotFoundException( addressesHex, nameof(MaterialItemSheet), materialInfo.Id); } if (requiredFungibleItems.ContainsKey(materialRow.Id)) { requiredFungibleItems[materialRow.Id] += materialInfo.Count; } else { requiredFungibleItems[materialRow.Id] = materialInfo.Count; } } // ~Validate Recipe Material costActionPoint += recipeRow.RequiredActionPoint; endBlockIndex += recipeRow.RequiredBlockIndex; // ~Validate Work // Remove Required Materials var inventory = avatarState.inventory; foreach (var pair in requiredFungibleItems.OrderBy(pair => pair.Key)) { if (!materialItemSheet.TryGetValue(pair.Key, out var materialRow) || !inventory.RemoveFungibleItem(materialRow.ItemId, context.BlockIndex, pair.Value)) { throw new NotEnoughMaterialException( $"{addressesHex}Aborted as the player has no enough material ({pair.Key} * {pair.Value})"); } } // ~Remove Required Materials // Subtract Required ActionPoint if (costActionPoint > 0) { if (avatarState.actionPoint < costActionPoint) { throw new NotEnoughActionPointException( $"{addressesHex}Aborted due to insufficient action point: {avatarState.actionPoint} < {costActionPoint}" ); } avatarState.actionPoint -= costActionPoint; } // ~Subtract Required ActionPoint // Create Consumable var consumable = (Consumable)ItemFactory.CreateItemUsable( consumableRow, context.Random.GenerateRandomGuid(), endBlockIndex ); // ~Create Consumable // Add or Update Consumable avatarState.blockIndex = context.BlockIndex; avatarState.updatedAt = context.BlockIndex; avatarState.UpdateFromCombination(consumable); avatarState.UpdateQuestRewards(materialItemSheet); // ~Add or Update Consumable // Update Slot var mailId = context.Random.GenerateRandomGuid(); var attachmentResult = new CombinationConsumable5.ResultModel { id = mailId, actionPoint = costActionPoint, materials = requiredFungibleItems.ToDictionary( e => ItemFactory.CreateMaterial(materialItemSheet, e.Key), e => e.Value), itemUsable = consumable, recipeId = recipeId, }; slotState.Update(attachmentResult, context.BlockIndex, endBlockIndex); // ~Update Slot // Create Mail var mail = new CombinationMail( attachmentResult, context.BlockIndex, mailId, endBlockIndex); avatarState.Update(mail); // ~Create Mail return(states .SetState(avatarAddress, avatarState.SerializeV2()) .SetState(inventoryAddress, avatarState.inventory.Serialize()) .SetState(worldInformationAddress, avatarState.worldInformation.Serialize()) .SetState(questListAddress, avatarState.questList.Serialize()) .SetState(slotAddress, slotState.Serialize())); }