예제 #1
0
        public override IAccountStateDelta Execute(IActionContext context)
        {
            var states = context.PreviousStates;

            if (context.Rehearsal)
            {
                return(states.SetState(avatarAddress, MarkChanged));
            }

            if (!states.TryGetAgentAvatarStates(context.Signer, avatarAddress, out var _, out var avatarState))
            {
                return(states);
            }

            var tableSheets = TableSheets.FromActionContext(context);
            var row         = tableSheets.MaterialItemSheet.Values.FirstOrDefault(r => r.ItemSubType == ItemSubType.ApStone);
            var apStone     = ItemFactory.CreateMaterial(row);

            if (!avatarState.inventory.RemoveFungibleItem(apStone))
            {
                Log.Error($"Not enough item {apStone}");
                return(states);
            }

            var gameConfigState = states.GetGameConfigState();

            if (gameConfigState is null)
            {
                return(states);
            }

            avatarState.actionPoint = gameConfigState.ActionPointMax;
            return(states.SetState(avatarAddress, avatarState.Serialize()));
        }
예제 #2
0
        public override IAccountStateDelta Execute(IActionContext context)
        {
            var states = context.PreviousStates;

            if (context.Rehearsal)
            {
                states = states.SetState(RedeemCodeState.Address, MarkChanged);
                states = states.SetState(avatarAddress, MarkChanged);
                states = states.SetState(context.Signer, MarkChanged);
                return(states);
            }

            if (!states.TryGetAgentAvatarStates(context.Signer, avatarAddress, out AgentState agentState,
                                                out AvatarState avatarState))
            {
                return(states);
            }

            var redeemState = states.GetRedeemCodeState();

            if (redeemState is null)
            {
                return(states);
            }

            int redeemId;

            try
            {
                redeemId = redeemState.Redeem(code, avatarAddress);
            }
            catch (InvalidRedeemCodeException)
            {
                Log.Error("Invalid Code");
                throw;
            }
            catch (DuplicateRedeemException e)
            {
                Log.Warning(e.Message);
                throw;
            }

            var tableSheets = TableSheets.FromActionContext(context);
            var row         = tableSheets.RedeemRewardSheet.Values.First(r => r.Id == redeemId);
            var rewards     = row.Rewards;
            var materialRow = tableSheets.MaterialItemSheet.Values.First(r => r.ItemSubType == ItemSubType.Chest);
            var chest       = ItemFactory.CreateChest(materialRow, rewards);

            avatarState.inventory.AddItem(chest, 1);
            states = states.SetState(avatarAddress, avatarState.Serialize());
            states = states.SetState(RedeemCodeState.Address, redeemState.Serialize());
            states = states.SetState(context.Signer, agentState.Serialize());
            return(states);
        }
예제 #3
0
        public void UpdateFromQuestReward(Quest.Quest quest, IActionContext context)
        {
            var items = new List <Material>();

            foreach (var pair in quest.Reward.ItemMap)
            {
                var row = TableSheets.FromActionContext(context)
                          .MaterialItemSheet.Values.First(itemRow => itemRow.Id == pair.Key);
                var item = ItemFactory.CreateMaterial(row);
                var map  = inventory.AddItem(item, pair.Value);
                itemMap.Add(map);
                items.Add(item);
            }

            quest.IsPaidInAction = true;
            questList.UpdateCollectQuest(itemMap);
            questList.UpdateItemTypeCollectQuest(items);
            UpdateCompletedQuest();
        }
예제 #4
0
        private static AvatarState CreateAvatarState(string name, Address avatarAddress, IActionContext ctx)
        {
            var tableSheets     = TableSheets.FromActionContext(ctx);
            var gameConfigState = ctx.PreviousStates.GetGameConfigState();
            var avatarState     = new AvatarState(
                avatarAddress,
                ctx.Signer,
                ctx.BlockIndex,
                tableSheets,
                gameConfigState,
                name
                );

            if (GameConfig.IsEditor)
            {
                AddItemsForTest(avatarState, ctx.Random, tableSheets);
            }

            return(avatarState);
        }
예제 #5
0
파일: RedeemCode.cs 프로젝트: x86chi/lib9c
        public override IAccountStateDelta Execute(IActionContext context)
        {
            var states = context.PreviousStates;

            if (context.Rehearsal)
            {
                states = states.SetState(RedeemCodeState.Address, MarkChanged);
                states = states.SetState(AvatarAddress, MarkChanged);
                states = states.SetState(context.Signer, MarkChanged);
                states = states.MarkBalanceChanged(GoldCurrencyMock, GoldCurrencyState.Address);
                states = states.MarkBalanceChanged(GoldCurrencyMock, context.Signer);
                return(states);
            }

            if (!states.TryGetAgentAvatarStates(context.Signer, AvatarAddress, out AgentState agentState,
                                                out AvatarState avatarState))
            {
                return(states);
            }

            var redeemState = states.GetRedeemCodeState();

            if (redeemState is null)
            {
                return(states);
            }

            int redeemId;

            try
            {
                redeemId = redeemState.Redeem(Code, AvatarAddress);
            }
            catch (InvalidRedeemCodeException)
            {
                Log.Error("Invalid Code");
                throw;
            }
            catch (DuplicateRedeemException e)
            {
                Log.Warning(e.Message);
                throw;
            }

            var tableSheets = TableSheets.FromActionContext(context);
            var row         = tableSheets.RedeemRewardSheet.Values.First(r => r.Id == redeemId);
            var rewards     = row.Rewards;

            var itemSheets = tableSheets.ItemSheet;

            foreach (RedeemRewardSheet.RewardInfo info in row.Rewards)
            {
                switch (info.Type)
                {
                case RewardType.Item:
                    for (var i = 0; i < info.Quantity; i++)
                    {
                        if (info.ItemId is int itemId)
                        {
                            ItemBase item = ItemFactory.CreateItem(itemSheets[itemId]);
                            // We should fix count as 1 because ItemFactory.CreateItem
                            // will create a new item every time.
                            avatarState.inventory.AddItem(item, 1);
                        }
                    }
                    states = states.SetState(AvatarAddress, avatarState.Serialize());
                    break;

                case RewardType.Gold:
                    states = states.TransferAsset(
                        GoldCurrencyState.Address,
                        context.Signer,
                        states.GetGoldCurrency() * info.Quantity
                        );
                    break;

                default:
                    // FIXME: We should raise exception here.
                    break;
                }
            }
            states = states.SetState(RedeemCodeState.Address, redeemState.Serialize());
            states = states.SetState(context.Signer, agentState.Serialize());
            return(states);
        }
예제 #6
0
        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));
            }

            var sw = new Stopwatch();

            sw.Start();
            var started = DateTimeOffset.UtcNow;

            Log.Debug("Combination exec started.");

            if (!states.TryGetAgentAvatarStates(ctx.Signer, AvatarAddress, out AgentState agentState,
                                                out AvatarState avatarState))
            {
                return(LogError(context, "Aborted as the avatar state of the signer was failed to load."));
            }

            sw.Stop();
            Log.Debug("Combination Get AgentAvatarStates: {Elapsed}", sw.Elapsed);
            sw.Restart();

            if (!avatarState.worldInformation.TryGetUnlockedWorldByStageClearedBlockIndex(out var world))
            {
                return(LogError(context, "Aborted as the WorldInformation was failed to load."));
            }

            if (world.StageClearedId < GameConfig.RequireClearedStageLevel.CombinationEquipmentAction)
            {
                // 스테이지 클리어 부족 에러.
                return(LogError(
                           context,
                           "Aborted as the signer is not cleared the minimum stage level required to combine consumables yet: {ClearedLevel} < {RequiredLevel}.",
                           world.StageClearedId,
                           GameConfig.RequireClearedStageLevel.CombinationEquipmentAction
                           ));
            }

            var slotState = states.GetCombinationSlotState(AvatarAddress, slotIndex);

            if (slotState is null || !(slotState.Validate(avatarState, ctx.BlockIndex)))
            {
                return(LogError(
                           context,
                           "Aborted as the slot state is failed to load or invalid: {@SlotState} @ {SlotIndex}",
                           slotState,
                           slotIndex
                           ));
            }

            var tableSheets = TableSheets.FromActionContext(ctx);

            sw.Stop();
            Log.Debug("Combination Get TableSheetsState: {Elapsed}", sw.Elapsed);
            sw.Restart();

            Log.Debug("Execute Combination; player: {Player}", AvatarAddress);
            var consumableItemSheet = tableSheets.ConsumableItemSheet;
            var recipeRow           = tableSheets.ConsumableItemRecipeSheet.Values.FirstOrDefault(r => r.Id == recipeId);

            if (recipeRow is null)
            {
                return(LogError(context, "Aborted as the recipe was failed to load."));
            }
            var materials = new Dictionary <Material, int>();

            foreach (var materialInfo in recipeRow.Materials)
            {
                var materialId = materialInfo.Id;
                var count      = materialInfo.Count;
                if (avatarState.inventory.HasItem(materialId, count))
                {
                    avatarState.inventory.TryGetFungibleItem(materialId, out var inventoryItem);
                    var material = (Material)inventoryItem.item;
                    materials[material] = count;
                    avatarState.inventory.RemoveFungibleItem(material, count);
                }
                else
                {
                    return(LogError(
                               context,
                               "Aborted as the player has no enough material ({Material} * {Quantity})",
                               materialId,
                               count
                               ));
                }
            }

            sw.Stop();
            Log.Debug("Combination Remove Materials: {Elapsed}", sw.Elapsed);
            sw.Restart();

            var result = new ResultModel
            {
                materials = materials,
                itemType  = ItemType.Consumable,
            };

            var costAP = recipeRow.RequiredActionPoint;

            if (avatarState.actionPoint < costAP)
            {
                // ap 부족 에러.
                return(LogError(
                           context,
                           "Aborted due to insufficient action point: {ActionPointBalance} < {ActionCost}",
                           avatarState.actionPoint,
                           costAP
                           ));
            }

            // ap 차감.
            avatarState.actionPoint -= costAP;
            result.actionPoint       = costAP;

            var resultConsumableItemId = recipeRow.ResultConsumableItemId;

            sw.Stop();
            Log.Debug("Combination Get Food id: {Elapsed}", sw.Elapsed);
            sw.Restart();
            result.recipeId = recipeRow.Id;

            if (!consumableItemSheet.TryGetValue(resultConsumableItemId, out var consumableItemRow))
            {
                // 소모품 테이블 값 가져오기 실패.
                return(LogError(
                           context,
                           "Aborted as the consumable item ({ItemId} was failed to load from the data table.",
                           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.UpdateFromCombination(itemUsable);
            sw.Stop();
            Log.Debug("Combination Update AvatarState: {Elapsed}", sw.Elapsed);
            sw.Restart();

            avatarState.UpdateQuestRewards(ctx);

            avatarState.updatedAt  = DateTimeOffset.UtcNow;
            avatarState.blockIndex = ctx.BlockIndex;
            states = states.SetState(AvatarAddress, avatarState.Serialize());
            slotState.Update(result, ctx.BlockIndex, requiredBlockIndex);
            sw.Stop();
            Log.Debug("Combination Set AvatarState: {Elapsed}", sw.Elapsed);
            var ended = DateTimeOffset.UtcNow;

            Log.Debug("Combination Total Executed Time: {Elapsed}", ended - started);
            return(states
                   .SetState(ctx.Signer, agentState.Serialize())
                   .SetState(slotAddress, slotState.Serialize()));
        }
예제 #7
0
        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));
            }

            if (!states.TryGetAgentAvatarStates(ctx.Signer, AvatarAddress, out var agentState,
                                                out var avatarState))
            {
                return(LogError(context, "Aborted as the avatar state of the signer was failed to load."));
            }

            var slotState = states.GetCombinationSlotState(AvatarAddress, SlotIndex);

            if (slotState is null || !(slotState.Validate(avatarState, ctx.BlockIndex)))
            {
                return(LogError(
                           context,
                           "Aborted as the slot state is failed to load or invalid: {@SlotState} @ {SlotIndex}",
                           slotState,
                           SlotIndex
                           ));
            }

            var tableSheets   = TableSheets.FromActionContext(ctx);
            var recipeSheet   = tableSheets.EquipmentItemRecipeSheet;
            var materialSheet = tableSheets.MaterialItemSheet;
            var materials     = new Dictionary <Material, int>();

            // 레시피 검증
            if (!recipeSheet.TryGetValue(RecipeId, out var recipe))
            {
                return(LogError(
                           context,
                           "Aborted as the recipe {RecipeId} was failed to load from the sheet.",
                           RecipeId
                           ));
            }

            if (!(SubRecipeId is null))
            {
                if (!recipe.SubRecipeIds.Contains((int)SubRecipeId))
                {
                    return(LogError(
                               context,
                               "Aborted as the subrecipe {SubRecipeId} was failed to load from the sheet.",
                               SubRecipeId
                               ));
                }
            }

            // 메인 레시피 해금 검사.
            if (!avatarState.worldInformation.IsStageCleared(recipe.UnlockStage))
            {
                return(LogError(
                           context,
                           "Aborted as the signer is not cleared the minimum stage level required to use the recipe {@Recipe} yet.",
                           recipe
                           ));
            }

            if (!materialSheet.TryGetValue(recipe.MaterialId, out var material))
            {
                return(LogError(
                           context,
                           "Aborted as the material {MaterialId} was failed to load from the sheet.",
                           recipe.MaterialId
                           ));
            }

            if (!avatarState.inventory.RemoveMaterial(material.ItemId, recipe.MaterialCount))
            {
                return(LogError(
                           context,
                           "Aborted as the player has no enough material ({Material} * {Quantity})",
                           material,
                           recipe.MaterialCount
                           ));
            }

            var equipmentMaterial = ItemFactory.CreateMaterial(materialSheet, material.Id);

            materials[equipmentMaterial] = recipe.MaterialCount;

            BigInteger requiredGold        = recipe.RequiredGold;
            var        requiredActionPoint = recipe.RequiredActionPoint;

            // 장비 제작
            if (!tableSheets.EquipmentItemSheet.TryGetValue(recipe.ResultEquipmentId, out var equipRow))
            {
                return(LogError(
                           context,
                           "Aborted as the equipment item {EquipmentId} was failed to load from the sheet.",
                           recipe.ResultEquipmentId
                           ));
            }

            var requiredBlockIndex = ctx.BlockIndex + recipe.RequiredBlockIndex;
            var equipment          = (Equipment)ItemFactory.CreateItemUsable(
                equipRow,
                ctx.Random.GenerateRandomGuid(),
                requiredBlockIndex
                );

            // 서브 레시피 검증
            HashSet <int> optionIds = null;

            if (SubRecipeId.HasValue)
            {
                var subSheet = tableSheets.EquipmentItemSubRecipeSheet;
                if (!subSheet.TryGetValue((int)SubRecipeId, out var subRecipe))
                {
                    return(LogError(
                               context,
                               "Aborted as the subrecipe {SubRecipeId} was failed to load from the subsheet.",
                               SubRecipeId
                               ));
                }

                // 서브 레시피 해금 검사.
                if (!avatarState.worldInformation.IsStageCleared(subRecipe.UnlockStage))
                {
                    return(LogError(
                               context,
                               "Aborted as the signer is not cleared the minimum stage level required to use the subrecipe {@SubRecipe} yet.",
                               subRecipe
                               ));
                }

                requiredBlockIndex  += subRecipe.RequiredBlockIndex;
                requiredGold        += subRecipe.RequiredGold;
                requiredActionPoint += subRecipe.RequiredActionPoint;

                foreach (var materialInfo in subRecipe.Materials)
                {
                    if (!materialSheet.TryGetValue(materialInfo.Id, out var subMaterialRow))
                    {
                        return(LogError(
                                   context,
                                   "Aborted as the meterial info {MaterialInfoId} was failed to load from the submaterial sheet.",
                                   materialInfo.Id
                                   ));
                    }

                    if (!avatarState.inventory.RemoveMaterial(subMaterialRow.ItemId,
                                                              materialInfo.Count))
                    {
                        return(LogError(
                                   context,
                                   "Aborted as the player has no enough material ({Material} * {Quantity})",
                                   subMaterialRow,
                                   materialInfo.Count
                                   ));
                    }

                    var subMaterial = ItemFactory.CreateMaterial(materialSheet, materialInfo.Id);
                    materials[subMaterial] = materialInfo.Count;
                }

                optionIds = SelectOption(tableSheets, subRecipe, ctx.Random, equipment);
                equipment.Update(requiredBlockIndex);
            }

            // 자원 검증
            BigInteger agentBalance = states.GetBalance(ctx.Signer, states.GetGoldCurrency());

            if (agentBalance < requiredGold || avatarState.actionPoint < requiredActionPoint)
            {
                return(LogError(
                           context,
                           "Aborted due to insufficient action point: {ActionPointBalance} < {ActionCost}",
                           avatarState.actionPoint,
                           requiredActionPoint
                           ));
            }

            avatarState.actionPoint -= requiredActionPoint;
            if (!(optionIds is null))
            {
                foreach (var id in optionIds)
                {
                    agentState.unlockedOptions.Add(id);
                }
            }

            // FIXME: BlacksmithAddress 계좌로 돈이 쌓이기만 하는데 이걸 어떻게 순환시킬지 기획이 필요.
            if (requiredGold > 0)
            {
                states = states.TransferAsset(ctx.Signer, BlacksmithAddress, states.GetGoldCurrency(), requiredGold);
            }

            var result = new CombinationConsumable.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, SubRecipeId);
            avatarState.UpdateFromCombination(equipment);
            avatarState.UpdateQuestRewards(ctx);
            return(states
                   .SetState(AvatarAddress, avatarState.Serialize())
                   .SetState(slotAddress, slotState.Serialize())
                   .SetState(ctx.Signer, agentState.Serialize()));
        }
예제 #8
0
        public override IAccountStateDelta Execute(IActionContext context)
        {
            var states = context.PreviousStates;

            if (context.Rehearsal)
            {
                states = states
                         .SetState(context.Signer, MarkChanged)
                         .SetState(avatarAddress, MarkChanged)
                         .MarkBalanceChanged(GoldCurrencyMock, GoldCurrencyState.Address, context.Signer);
                return(states);
            }

            if (!states.TryGetAgentAvatarStates(context.Signer, avatarAddress, out AgentState _,
                                                out AvatarState avatarState))
            {
                // FIXME: 오류 처리 필요하지 않나요?
            }

            var tableSheets = TableSheets.FromActionContext(context);

            foreach (var pair in chestList)
            {
                var itemId = pair.Key;
                var count  = pair.Value;
                if (avatarState.inventory.TryGetMaterial(itemId, out var inventoryItem) && inventoryItem.count >= count)
                {
                    var chest = (Chest)inventoryItem.item;
                    foreach (var info in chest.Rewards)
                    {
                        switch (info.Type)
                        {
                        case RewardType.Item:
                            var itemRow =
                                tableSheets.MaterialItemSheet.Values.FirstOrDefault(r => r.Id == info.ItemId);
                            if (itemRow is null)
                            {
                                continue;
                            }
                            var material = ItemFactory.CreateMaterial(itemRow);
                            avatarState.inventory.AddItem(material, info.Quantity);
                            break;

                        case RewardType.Gold:
                            states = states.TransferAsset(
                                GoldCurrencyState.Address,
                                context.Signer,
                                states.GetGoldCurrency() * info.Quantity
                                );
                            break;

                        default:
                            throw new ArgumentOutOfRangeException(nameof(info.Type), info.Type, null);
                        }
                    }

                    avatarState.inventory.RemoveMaterial(itemId, count);
                }
            }

            states = states.SetState(avatarAddress, avatarState.Serialize());
            return(states);
        }
예제 #9
0
        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
                       .MarkBalanceChanged(GoldCurrencyMock, BlacksmithAddress)
                       .SetState(avatarAddress, MarkChanged)
                       .SetState(slotAddress, MarkChanged));
            }
            var sw = new Stopwatch();

            sw.Start();
            var started = DateTimeOffset.UtcNow;

            Log.Debug("ItemEnhancement exec started.");

            if (!states.TryGetAgentAvatarStates(ctx.Signer, avatarAddress, out AgentState agentState,
                                                out AvatarState avatarState))
            {
                return(LogError(context, "Aborted as the avatar state of the signer was failed to load."));
            }
            sw.Stop();
            Log.Debug("ItemEnhancement Get AgentAvatarStates: {Elapsed}", sw.Elapsed);
            sw.Restart();

            if (!avatarState.inventory.TryGetNonFungibleItem(itemId, out ItemUsable enhancementItem))
            {
                // 강화 장비가 없는 에러.
                return(LogError(
                           context,
                           "Aborted as the NonFungibleItem ({ItemId}) was failed to load from avatar's inventory.",
                           itemId
                           ));
            }

            if (enhancementItem.RequiredBlockIndex > context.BlockIndex)
            {
                return(LogError(
                           context,
                           "Aborted as the equipment to enhance ({ItemId}) is not available yet; it will be available at the block #{RequiredBlockIndex}.",
                           itemId,
                           enhancementItem.RequiredBlockIndex
                           ));
            }

            if (!(enhancementItem is Equipment enhancementEquipment))
            {
                // 캐스팅 버그. 예외상황.
                return(LogError(
                           context,
                           $"Aborted as the item is not a {nameof(Equipment)}, but {{ItemType}}.",
                           enhancementItem.GetType().Name
                           ));
            }

            var slotState = states.GetCombinationSlotState(avatarAddress, slotIndex);

            if (slotState is null || !(slotState.Validate(avatarState, ctx.BlockIndex)))
            {
                return(LogError(context, "Aborted as the slot state was failed to load or invalid."));
            }

            sw.Stop();
            Log.Debug("ItemEnhancement Get Equipment: {Elapsed}", sw.Elapsed);
            sw.Restart();

            if (enhancementEquipment.level > 9)
            {
                // 최대 강화도 초과 에러.
                return(LogError(
                           context,
                           "Aborted due to invaild equipment level: {EquipmentLevel} < 9",
                           enhancementEquipment.level
                           ));
            }

            var result = new ResultModel
            {
                itemUsable         = enhancementEquipment,
                materialItemIdList = materialIds
            };

            var requiredAP = GetRequiredAp();

            if (avatarState.actionPoint < requiredAP)
            {
                // AP 부족 에러.
                return(LogError(
                           context,
                           "Aborted due to insufficient action point: {ActionPointBalance} < {ActionCost}",
                           avatarState.actionPoint,
                           requiredAP
                           ));
            }

            var tableSheets = TableSheets.FromActionContext(ctx);
            var requiredNCG = GetRequiredNCG(tableSheets, enhancementEquipment.Grade, enhancementEquipment.level + 1);

            avatarState.actionPoint -= requiredAP;
            result.actionPoint       = requiredAP;

            if (requiredNCG > 0)
            {
                states = states.TransferAsset(ctx.Signer, BlacksmithAddress, states.GetGoldCurrency(), requiredNCG);
            }

            sw.Stop();
            Log.Debug("ItemEnhancement Get TableSheets: {Elapsed}", sw.Elapsed);
            sw.Restart();
            var materials = new List <Equipment>();

            foreach (var materialId in materialIds)
            {
                if (!avatarState.inventory.TryGetNonFungibleItem(materialId, out ItemUsable materialItem))
                {
                    // 인벤토리에 재료로 등록한 장비가 없는 에러.
                    return(LogError(
                               context,
                               "Aborted as the the signer does not have a necessary material ({MaterialId}).",
                               materialId
                               ));
                }

                if (materialItem.RequiredBlockIndex > context.BlockIndex)
                {
                    return(LogError(
                               context,
                               "Aborted as the material ({MaterialId}) is not available yet; it will be available at the block #{RequiredBlockIndex}.",
                               materialId,
                               materialItem.RequiredBlockIndex
                               ));
                }

                if (!(materialItem is Equipment materialEquipment))
                {
                    return(LogError(
                               context,
                               $"Aborted as the material item is not a {nameof(Equipment)}, but {{ItemType}}.",
                               materialItem.GetType().Name
                               ));
                }

                if (materials.Contains(materialEquipment))
                {
                    // 같은 guid의 아이템이 중복해서 등록된 에러.
                    return(LogError(
                               context,
                               "Aborted as the same material was used more than once: {Material}",
                               materialEquipment
                               ));
                }

                if (enhancementEquipment.ItemId == materialId)
                {
                    // 강화 장비와 재료로 등록한 장비가 같은 에러.
                    return(LogError(
                               context,
                               "Aborted as an equipment to enhance ({ItemId}) was used as a material too.",
                               materialId
                               ));
                }

                if (materialEquipment.ItemSubType != enhancementEquipment.ItemSubType)
                {
                    // 서브 타입이 다른 에러.
                    return(LogError(
                               context,
                               "Aborted as the material item is not a {ExpectedItemSubType}, but {MaterialSubType}.",
                               enhancementEquipment.ItemSubType,
                               materialEquipment.ItemSubType
                               ));
                }

                if (materialEquipment.Grade != enhancementEquipment.Grade)
                {
                    // 등급이 다른 에러.
                    return(LogError(
                               context,
                               "Aborted as grades of the equipment to enhance ({EquipmentGrade}) and a material ({MaterialGrade}) do not match.",
                               enhancementEquipment.Grade,
                               materialEquipment.Grade
                               ));
                }

                if (materialEquipment.level != enhancementEquipment.level)
                {
                    // 강화도가 다른 에러.
                    return(LogError(
                               context,
                               "Aborted as levels of the equipment to enhance ({EquipmentLevel}) and a material ({MaterialLevel}) do not match.",
                               enhancementEquipment.level,
                               materialEquipment.level
                               ));
                }
                sw.Stop();
                Log.Debug("ItemEnhancement Get Material: {Elapsed}", sw.Elapsed);
                sw.Restart();
                materialEquipment.Unequip();
                materials.Add(materialEquipment);
            }

            enhancementEquipment.Unequip();

            enhancementEquipment = UpgradeEquipment(enhancementEquipment);
            sw.Stop();
            Log.Debug("ItemEnhancement Upgrade Equipment: {Elapsed}", sw.Elapsed);
            sw.Restart();

            result.gold = 0;

            foreach (var material in materials)
            {
                avatarState.inventory.RemoveNonFungibleItem(material);
            }
            sw.Stop();
            Log.Debug("ItemEnhancement Remove Materials: {Elapsed}", sw.Elapsed);
            sw.Restart();
            var mail = new ItemEnhanceMail(result, ctx.BlockIndex, ctx.Random.GenerateRandomGuid(), ctx.BlockIndex);

            result.id = mail.id;

            avatarState.inventory.RemoveNonFungibleItem(enhancementEquipment);
            avatarState.Update(mail);
            avatarState.UpdateFromItemEnhancement(enhancementEquipment);

            avatarState.UpdateQuestRewards(ctx);

            slotState.Update(result, ctx.BlockIndex, ctx.BlockIndex);

            sw.Stop();
            Log.Debug("ItemEnhancement Update AvatarState: {Elapsed}", sw.Elapsed);
            sw.Restart();
            states = states.SetState(avatarAddress, avatarState.Serialize());
            sw.Stop();
            Log.Debug("ItemEnhancement Set AvatarState: {Elapsed}", sw.Elapsed);
            var ended = DateTimeOffset.UtcNow;

            Log.Debug("ItemEnhancement Total Executed Time: {Elapsed}", ended - started);
            return(states.SetState(slotAddress, slotState.Serialize()));
        }
예제 #10
0
        public override IAccountStateDelta Execute(IActionContext context)
        {
            var states      = context.PreviousStates;
            var slotAddress = avatarAddress.Derive(
                string.Format(
                    CultureInfo.InvariantCulture,
                    CombinationSlotState.DeriveFormat,
                    slotIndex
                    )
                );

            if (context.Rehearsal)
            {
                return(states
                       .SetState(avatarAddress, MarkChanged)
                       .SetState(slotAddress, MarkChanged));
            }

            if (!states.TryGetAgentAvatarStates(context.Signer, avatarAddress,
                                                out var agentState, out var avatarState))
            {
                return(states);
            }

            var slotState = states.GetCombinationSlotState(avatarAddress, slotIndex);

            if (slotState?.Result is null)
            {
                Log.Warning("CombinationSlot Result is null.");
                return(states);
            }
            if (slotState.UnlockBlockIndex <= context.BlockIndex)
            {
                Log.Warning($"Can't use combination slot. it unlock on {slotState.UnlockBlockIndex} block.");
                return(states);
            }

            var gameConfigState = states.GetGameConfigState();

            if (gameConfigState is null)
            {
                return(states);
            }

            var diff = slotState.Result.itemUsable.RequiredBlockIndex - context.BlockIndex;

            if (diff < 0)
            {
                Log.Information("Skip rapid combination.");
                return(states);
            }

            var count       = CalculateHourglassCount(gameConfigState, diff);
            var tableSheets = TableSheets.FromActionContext(context);
            var row         = tableSheets.MaterialItemSheet.Values.First(r => r.ItemSubType == ItemSubType.Hourglass);
            var hourGlass   = ItemFactory.CreateMaterial(row);

            if (!avatarState.inventory.RemoveFungibleItem(hourGlass, count))
            {
                Log.Error($"Not enough item {hourGlass} : {count}");
                return(states);
            }

            slotState.Update(context.BlockIndex, hourGlass, count);
            avatarState.UpdateFromRapidCombination(
                ((CombinationConsumable.ResultModel)slotState.Result),
                context.BlockIndex
                );
            return(states
                   .SetState(avatarAddress, avatarState.Serialize())
                   .SetState(slotAddress, slotState.Serialize()));
        }