From ded7ce1dcf3b7ef75fc557a98416617da5303634 Mon Sep 17 00:00:00 2001 From: ImmuState <1244229+kyoko12@users.noreply.github.com> Date: Tue, 14 Jun 2022 10:14:15 +0200 Subject: [PATCH] Implement Resin (#1257) * Basic resin usage/refresh. * Honor resin config, move some logic to logon. * Add resin usage to DungeonChallenge * Make fragile and transient resin usable. * Get resin cost from dungeon excel. * Add ability to unlock combine diagrams. * Refactor CombineManager to use Inventory.payItems, enabling crafting of condensed resin. * Refactor ForgingManager to use Inventory.payItems, to prepare for eventually forging Mystic Enhancement Ores using resin. * Remove comment * Check resin usage in addResin --- .../grasscutter/data/excels/DungeonData.java | 11 ++ .../game/combine/CombineManger.java | 62 ++++---- .../game/dungeons/DungeonChallenge.java | 52 ++++++- .../grasscutter/game/inventory/Inventory.java | 7 + .../ForgingManager/ForgingManager.java | 33 +--- .../game/managers/InventoryManager.java | 21 +++ .../game/managers/ResinManager.java | 143 ++++++++++++++++++ .../emu/grasscutter/game/player/Player.java | 33 +++- .../packet/recv/HandlerGadgetInteractReq.java | 2 +- .../packet/send/PacketCombineDataNotify.java | 18 +++ .../send/PacketCombineFormulaDataNotify.java | 19 +++ .../packet/send/PacketResinChangeNotify.java | 23 +++ .../grasscutter/utils/ConfigContainer.java | 7 + 13 files changed, 370 insertions(+), 61 deletions(-) create mode 100644 src/main/java/emu/grasscutter/game/managers/ResinManager.java create mode 100644 src/main/java/emu/grasscutter/server/packet/send/PacketCombineDataNotify.java create mode 100644 src/main/java/emu/grasscutter/server/packet/send/PacketCombineFormulaDataNotify.java create mode 100644 src/main/java/emu/grasscutter/server/packet/send/PacketResinChangeNotify.java diff --git a/src/main/java/emu/grasscutter/data/excels/DungeonData.java b/src/main/java/emu/grasscutter/data/excels/DungeonData.java index 3b18c01ef..970e7628e 100644 --- a/src/main/java/emu/grasscutter/data/excels/DungeonData.java +++ b/src/main/java/emu/grasscutter/data/excels/DungeonData.java @@ -15,6 +15,9 @@ public class DungeonData extends GameResource { private String involveType; // TODO enum private RewardPreviewData previewData; + + private int statueCostID; + private int statueCostCount; @Override public int getId() { @@ -33,6 +36,14 @@ public class DungeonData extends GameResource { return previewData; } + public int getStatueCostID() { + return statueCostID; + } + + public int getStatueCostCount() { + return statueCostCount; + } + @Override public void onLoad() { if (this.passRewardPreviewID > 0) { diff --git a/src/main/java/emu/grasscutter/game/combine/CombineManger.java b/src/main/java/emu/grasscutter/game/combine/CombineManger.java index 641a69a0c..c1636da0d 100644 --- a/src/main/java/emu/grasscutter/game/combine/CombineManger.java +++ b/src/main/java/emu/grasscutter/game/combine/CombineManger.java @@ -3,13 +3,17 @@ package emu.grasscutter.game.combine; import emu.grasscutter.data.GameData; import emu.grasscutter.data.common.ItemParamData; import emu.grasscutter.data.excels.CombineData; +import emu.grasscutter.game.inventory.GameItem; import emu.grasscutter.game.inventory.ItemType; import emu.grasscutter.game.player.Player; +import emu.grasscutter.game.props.ActionReason; import emu.grasscutter.net.proto.RetcodeOuterClass; import emu.grasscutter.server.game.GameServer; +import emu.grasscutter.server.packet.send.PacketCombineFormulaDataNotify; import emu.grasscutter.server.packet.send.PacketCombineRsp; import it.unimi.dsi.fastutil.Pair; +import java.util.ArrayList; import java.util.List; public class CombineManger { @@ -23,6 +27,27 @@ public class CombineManger { this.gameServer = gameServer; } + public boolean unlockCombineDiagram(Player player, GameItem diagramItem) { + // Make sure this is actually a diagram. + if (!diagramItem.getItemData().getItemUse().get(0).getUseOp().equals("ITEM_USE_UNLOCK_COMBINE")) { + return false; + } + + // Determine the combine item we should unlock. + int combineId = Integer.parseInt(diagramItem.getItemData().getItemUse().get(0).getUseParam().get(0)); + + // Remove the diagram from the player's inventory. + // We need to do this here, before sending CombineFormulaDataNotify, or the the combine UI won't correctly + // update when unlocking the diagram. + player.getInventory().removeItem(diagramItem, 1); + + // Tell the client that this diagram is now unlocked and add the unlocked item to the player. + player.getUnlockedCombines().add(combineId); + player.sendPacket(new PacketCombineFormulaDataNotify(combineId)); + + return true; + } + public CombineResult combineItem(Player player, int cid, int count){ // check config exist if(!GameData.getCombineDataMap().containsKey(cid)){ @@ -35,36 +60,17 @@ public class CombineManger { if(combineData.getPlayerLevel() > player.getLevel()){ return null; } - // check enough - var enough = combineData.getMaterialItems().stream() - .filter(item -> player.getInventory() - .getInventoryTab(ItemType.ITEM_MATERIAL) - .getItemById(item.getId()) - .getCount() < item.getCount() * count - ) - .findAny() - .isEmpty(); - // if not enough - if(!enough){ - player.getWorld().getHost().sendPacket( - new PacketCombineRsp(RetcodeOuterClass.Retcode.RET_ITEM_COMBINE_COUNT_NOT_ENOUGH_VALUE) - ); - return null; + // consume items + List material = new ArrayList<>(combineData.getMaterialItems()); + material.add(new ItemParamData(202, combineData.getScoinCost())); + + boolean success = player.getInventory().payItems(material.toArray(new ItemParamData[0]), count, ActionReason.Combine); + + // abort if not enough material + if (!success) { + player.sendPacket(new PacketCombineRsp(RetcodeOuterClass.Retcode.RET_ITEM_COMBINE_COUNT_NOT_ENOUGH_VALUE)); } - if (player.getMora() >= combineData.getScoinCost()) { - player.setMora(player.getMora() - combineData.getScoinCost() * count); - } else { - return null; - } - // try to remove materials - combineData.getMaterialItems().stream() - .map(item -> Pair.of(player.getInventory() - .getInventoryTab(ItemType.ITEM_MATERIAL) - .getItemById(item.getId()) - ,item.getCount() * count) - ) - .forEach(item -> player.getInventory().removeItem(item.first(), item.second())); // make the result player.getInventory().addItem(combineData.getResultItemId(), diff --git a/src/main/java/emu/grasscutter/game/dungeons/DungeonChallenge.java b/src/main/java/emu/grasscutter/game/dungeons/DungeonChallenge.java index fbaf26cc5..ee3d18b3f 100644 --- a/src/main/java/emu/grasscutter/game/dungeons/DungeonChallenge.java +++ b/src/main/java/emu/grasscutter/game/dungeons/DungeonChallenge.java @@ -4,9 +4,11 @@ import emu.grasscutter.data.common.ItemParamData; import emu.grasscutter.data.excels.DungeonData; import emu.grasscutter.game.entity.EntityMonster; import emu.grasscutter.game.inventory.GameItem; +import emu.grasscutter.game.inventory.ItemType; import emu.grasscutter.game.player.Player; import emu.grasscutter.game.props.ActionReason; import emu.grasscutter.game.world.Scene; +import emu.grasscutter.net.proto.GadgetInteractReqOuterClass.GadgetInteractReq; import emu.grasscutter.scripts.constants.EventType; import emu.grasscutter.scripts.data.SceneGroup; import emu.grasscutter.scripts.data.ScriptArgs; @@ -153,8 +155,20 @@ public class DungeonChallenge { } } - public void getStatueDrops(Player player) { + private List rollRewards() { + List rewards = new ArrayList<>(); + + for (ItemParamData param : getScene().getDungeonData().getRewardPreview().getPreviewItems()) { + rewards.add(new GameItem(param.getId(), Math.max(param.getCount(), 1))); + } + + return rewards; + } + + public void getStatueDrops(Player player, GadgetInteractReq request) { DungeonData dungeonData = getScene().getDungeonData(); + int resinCost = dungeonData.getStatueCostCount() != 0 ? dungeonData.getStatueCostCount() : 20; + if (!isSuccess() || dungeonData == null || dungeonData.getRewardPreview() == null || dungeonData.getRewardPreview().getPreviewItems().length == 0) { return; } @@ -164,11 +178,43 @@ public class DungeonChallenge { return; } + // Get rewards. List rewards = new ArrayList<>(); - for (ItemParamData param : getScene().getDungeonData().getRewardPreview().getPreviewItems()) { - rewards.add(new GameItem(param.getId(), Math.max(param.getCount(), 1))); + + if (request.getIsUseCondenseResin()) { + // Check if condensed resin is usable here. + // For this, we use the following logic for now: + // The normal resin cost of the dungeon has to be 20. + if (resinCost != 20) { + return; + } + + // Make sure the player has condensed resin. + GameItem condensedResin = player.getInventory().getInventoryTab(ItemType.ITEM_MATERIAL).getItemById(220007); + if (condensedResin == null || condensedResin.getCount() <= 0) { + return; + } + + // Deduct. + player.getInventory().removeItem(condensedResin, 1); + + // Roll rewards, twice (because condensed). + rewards.addAll(this.rollRewards()); + rewards.addAll(this.rollRewards()); + } + else { + // If the player used regular resin, try to deduct. + // Stop if insufficient resin. + boolean success = player.getResinManager().useResin(resinCost); + if (!success) { + return; + } + + // Roll rewards. + rewards.addAll(this.rollRewards()); } + // Add rewards to player and send notification. player.getInventory().addItems(rewards, ActionReason.DungeonStatueDrop); player.sendPacket(new PacketGadgetAutoPickDropInfoNotify(rewards)); diff --git a/src/main/java/emu/grasscutter/game/inventory/Inventory.java b/src/main/java/emu/grasscutter/game/inventory/Inventory.java index e175aef01..f870ba113 100644 --- a/src/main/java/emu/grasscutter/game/inventory/Inventory.java +++ b/src/main/java/emu/grasscutter/game/inventory/Inventory.java @@ -17,6 +17,7 @@ import emu.grasscutter.game.avatar.AvatarStorage; import emu.grasscutter.game.avatar.Avatar; import emu.grasscutter.game.player.Player; import emu.grasscutter.game.props.ActionReason; +import emu.grasscutter.game.props.PlayerProperty; import emu.grasscutter.net.proto.ItemParamOuterClass.ItemParam; import emu.grasscutter.server.packet.send.PacketAvatarEquipChangeNotify; import emu.grasscutter.server.packet.send.PacketItemAddHintNotify; @@ -258,6 +259,8 @@ public class Inventory implements Iterable { getPlayer().addExpDirectly(count); case 105 -> // Companionship exp getPlayer().getServer().getInventoryManager().upgradeAvatarFetterLevel(player, getPlayer().getTeamManager().getCurrentAvatarEntity().getAvatar(), count); + case 106 -> // Resin + getPlayer().getResinManager().addResin(count); case 201 -> // Primogem getPlayer().setPrimogems(player.getPrimogems() + count); case 202 -> // Mora @@ -275,6 +278,8 @@ public class Inventory implements Iterable { return player.getMora(); case 203: // Genesis Crystals return player.getCrystals(); + case 106: // Resin + return player.getProperty(PlayerProperty.PROP_PLAYER_RESIN); default: GameItem item = getInventoryTab(ItemType.ITEM_MATERIAL).getItemById(itemId); // What if we ever want to operate on weapons/relics/furniture? :S return (item == null) ? 0 : item.getCount(); @@ -313,6 +318,8 @@ public class Inventory implements Iterable { player.setMora(player.getMora() - (cost.getCount() * quantity)); case 203 -> // Genesis Crystals player.setCrystals(player.getCrystals() - (cost.getCount() * quantity)); + case 106 -> // Resin + player.getResinManager().useResin(cost.getCount() * quantity); default -> removeItem(getInventoryTab(ItemType.ITEM_MATERIAL).getItemById(cost.getId()), cost.getCount() * quantity); } diff --git a/src/main/java/emu/grasscutter/game/managers/ForgingManager/ForgingManager.java b/src/main/java/emu/grasscutter/game/managers/ForgingManager/ForgingManager.java index cee06ca31..7f82f869f 100644 --- a/src/main/java/emu/grasscutter/game/managers/ForgingManager/ForgingManager.java +++ b/src/main/java/emu/grasscutter/game/managers/ForgingManager/ForgingManager.java @@ -15,6 +15,7 @@ import emu.grasscutter.data.excels.ItemData; import emu.grasscutter.game.inventory.GameItem; import emu.grasscutter.game.inventory.ItemType; import emu.grasscutter.game.player.Player; +import emu.grasscutter.game.props.ActionReason; import emu.grasscutter.net.proto.ForgeStartReqOuterClass; import emu.grasscutter.net.proto.ForgeQueueDataOuterClass.ForgeQueueData; import emu.grasscutter.net.proto.ForgeQueueManipulateReqOuterClass.ForgeQueueManipulateReq; @@ -147,38 +148,16 @@ public class ForgingManager { ForgeData forgeData = GameData.getForgeDataMap().get(req.getForgeId()); - // Check if we have enough of each material. - for (var material : forgeData.getMaterialItems()) { - if (material.getItemId() == 0) { - continue; - } + // Check if we have enough of each material and consume. + List material = new ArrayList<>(forgeData.getMaterialItems()); + material.add(new ItemParamData(202, forgeData.getScoinCost())); - int currentCount = this.player.getInventory().getInventoryTab(ItemType.ITEM_MATERIAL).getItemById(material.getItemId()).getCount(); + boolean success = player.getInventory().payItems(material.toArray(new ItemParamData[0]), req.getForgeCount(), ActionReason.ForgeCost); - if (currentCount < material.getCount() * req.getForgeCount()) { - this.player.sendPacket(new PacketForgeStartRsp(Retcode.RET_FORGE_POINT_NOT_ENOUGH)); //ToDo: Probably the wrong return code. - return; - } - } - - // Check if we have enough Mora. - if (this.player.getMora() < forgeData.getScoinCost() * req.getForgeCount()) { + if (!success) { this.player.sendPacket(new PacketForgeStartRsp(Retcode.RET_FORGE_POINT_NOT_ENOUGH)); //ToDo: Probably the wrong return code. - return; } - // Consume material and Mora. - for (var material : forgeData.getMaterialItems()) { - if (material.getItemId() == 0) { - continue; - } - - GameItem item = this.player.getInventory().getInventoryTab(ItemType.ITEM_MATERIAL).getItemById(material.getItemId()); - this.player.getInventory().removeItem(item, material.getCount() * req.getForgeCount()); - } - - this.player.setMora(this.player.getMora() - forgeData.getScoinCost() * req.getForgeCount()); - // Create and add active forge. ActiveForgeData activeForge = new ActiveForgeData(); activeForge.setForgeId(req.getForgeId()); diff --git a/src/main/java/emu/grasscutter/game/managers/InventoryManager.java b/src/main/java/emu/grasscutter/game/managers/InventoryManager.java index 33df0ecf3..b73cb3dd4 100644 --- a/src/main/java/emu/grasscutter/game/managers/InventoryManager.java +++ b/src/main/java/emu/grasscutter/game/managers/InventoryManager.java @@ -856,6 +856,27 @@ public class InventoryManager { // Unlock. useSuccess = player.getForgingManager().unlockForgingBlueprint(useItem); } + // Handle combine diagrams. + if (useItem.getItemData().getItemUse().get(0).getUseOp().equals("ITEM_USE_UNLOCK_COMBINE")) { + // Unlock. + useSuccess = player.getServer().getCombineManger().unlockCombineDiagram(player, useItem); + } + break; + case MATERIAL_CONSUME_BATCH_USE: + // Make sure we have usage data for this material. + if (useItem.getItemData().getItemUse() == null) { + break; + } + + // Handle fragile/transient resin. + if (useItem.getItemId() == 107009 || useItem.getItemId() == 107012){ + // Add resin to the inventory. + ItemData resinItemData = GameData.getItemDataMap().get(106); + player.getInventory().addItem(new GameItem(resinItemData, 60 * count), ActionReason.PlayerUseItem); + + // Set used amount. + used = count; + } break; case MATERIAL_CHEST: List shopChestTableList = player.getServer().getShopManager().getShopChestData(); diff --git a/src/main/java/emu/grasscutter/game/managers/ResinManager.java b/src/main/java/emu/grasscutter/game/managers/ResinManager.java new file mode 100644 index 000000000..cefd0ede8 --- /dev/null +++ b/src/main/java/emu/grasscutter/game/managers/ResinManager.java @@ -0,0 +1,143 @@ +package emu.grasscutter.game.managers; + +import emu.grasscutter.game.player.Player; +import emu.grasscutter.game.props.PlayerProperty; +import emu.grasscutter.server.packet.send.PacketPlayerPropNotify; +import emu.grasscutter.server.packet.send.PacketResinChangeNotify; +import emu.grasscutter.utils.Utils; + +import static emu.grasscutter.Configuration.GAME_OPTIONS; + +public class ResinManager { + private final Player player; + + public ResinManager(Player player) { + this.player = player; + } + + /******************** + * Change resin. + ********************/ + public synchronized boolean useResin(int amount) { + // Check if resin enabled. + if (!GAME_OPTIONS.resinOptions.resinUsage) { + return true; + } + + int currentResin = this.player.getProperty(PlayerProperty.PROP_PLAYER_RESIN); + + // Check if the player has sufficient resin. + if (currentResin < amount) { + return false; + } + + // Deduct the resin from the player. + int newResin = currentResin - amount; + this.player.setProperty(PlayerProperty.PROP_PLAYER_RESIN, newResin); + + // Check if this has taken the player under the recharge cap, + // starting the recharging process. + if (this.player.getNextResinRefresh() == 0 && newResin < GAME_OPTIONS.resinOptions.cap) { + int currentTime = Utils.getCurrentSeconds(); + this.player.setNextResinRefresh(currentTime + GAME_OPTIONS.resinOptions.rechargeTime); + } + + // Send packets. + this.player.sendPacket(new PacketPlayerPropNotify(this.player, PlayerProperty.PROP_PLAYER_RESIN)); + this.player.sendPacket(new PacketResinChangeNotify(this.player)); + + return true; + } + + public synchronized void addResin(int amount) { + // Check if resin enabled. + if (!GAME_OPTIONS.resinOptions.resinUsage) { + return; + } + + // Add resin. + int currentResin = this.player.getProperty(PlayerProperty.PROP_PLAYER_RESIN); + int newResin = currentResin + amount; + this.player.setProperty(PlayerProperty.PROP_PLAYER_RESIN, newResin); + + // Stop recharging if player is now at or over the cap. + if (newResin >= GAME_OPTIONS.resinOptions.cap) { + this.player.setNextResinRefresh(0); + } + + // Send packets. + this.player.sendPacket(new PacketPlayerPropNotify(this.player, PlayerProperty.PROP_PLAYER_RESIN)); + this.player.sendPacket(new PacketResinChangeNotify(this.player)); + } + + /******************** + * Recharge resin. + ********************/ + public synchronized void rechargeResin() { + // Check if resin enabled. + if (!GAME_OPTIONS.resinOptions.resinUsage) { + return; + } + + int currentResin = this.player.getProperty(PlayerProperty.PROP_PLAYER_RESIN); + int currentTime = Utils.getCurrentSeconds(); + + // Make sure we are currently in "recharging mode". + // This is denoted by Player.nextResinRefresh being greater than 0. + if (this.player.getNextResinRefresh() <= 0) { + return; + } + + // Determine if we actually need to recharge yet. + if (currentTime < this.player.getNextResinRefresh()) { + return; + } + + // Calculate how much resin we need to refill and update player. + // Note that this can be more than one in case the player + // logged off with uncapped resin and is now logging in again. + int recharge = 1 + (int)((currentTime - this.player.getNextResinRefresh()) / GAME_OPTIONS.resinOptions.rechargeTime); + int newResin = Math.min(GAME_OPTIONS.resinOptions.cap, currentResin + recharge); + int resinChange = newResin - currentResin; + + this.player.setProperty(PlayerProperty.PROP_PLAYER_RESIN, newResin); + + // Calculate next recharge time. + // Set to zero to disable recharge (because on/over cap.) + if (newResin >= GAME_OPTIONS.resinOptions.cap) { + this.player.setNextResinRefresh(0); + } + else { + int nextRecharge = this.player.getNextResinRefresh() + resinChange * GAME_OPTIONS.resinOptions.rechargeTime; + this.player.setNextResinRefresh(nextRecharge); + } + + // Send packets. + this.player.sendPacket(new PacketPlayerPropNotify(this.player, PlayerProperty.PROP_PLAYER_RESIN)); + this.player.sendPacket(new PacketResinChangeNotify(this.player)); + } + + /******************** + * Player login. + ********************/ + public synchronized void onPlayerLogin() { + // If resin usage is disabled, set resin to cap. + if (!GAME_OPTIONS.resinOptions.resinUsage) { + this.player.setProperty(PlayerProperty.PROP_PLAYER_RESIN, GAME_OPTIONS.resinOptions.cap); + this.player.setNextResinRefresh(0); + } + + // In case server administrators change the resin cap while players are capped, + // we need to restart recharging here. + int currentResin = this.player.getProperty(PlayerProperty.PROP_PLAYER_RESIN); + int currentTime = Utils.getCurrentSeconds(); + + if (currentResin < GAME_OPTIONS.resinOptions.cap && this.player.getNextResinRefresh() == 0) { + this.player.setNextResinRefresh(currentTime + GAME_OPTIONS.resinOptions.rechargeTime); + } + + // Send initial notifications on logon. + this.player.sendPacket(new PacketPlayerPropNotify(this.player, PlayerProperty.PROP_PLAYER_RESIN)); + this.player.sendPacket(new PacketResinChangeNotify(this.player)); + } +} diff --git a/src/main/java/emu/grasscutter/game/player/Player.java b/src/main/java/emu/grasscutter/game/player/Player.java index b0bb3cff3..5f1c93bff 100644 --- a/src/main/java/emu/grasscutter/game/player/Player.java +++ b/src/main/java/emu/grasscutter/game/player/Player.java @@ -27,6 +27,7 @@ import emu.grasscutter.game.inventory.Inventory; import emu.grasscutter.game.mail.Mail; import emu.grasscutter.game.mail.MailHandler; import emu.grasscutter.game.managers.InsectCaptureManager; +import emu.grasscutter.game.managers.ResinManager; import emu.grasscutter.game.managers.StaminaManager.StaminaManager; import emu.grasscutter.game.managers.SotSManager; import emu.grasscutter.game.managers.EnergyManager.EnergyManager; @@ -48,6 +49,7 @@ import emu.grasscutter.net.proto.*; import emu.grasscutter.net.proto.AbilityInvokeEntryOuterClass.AbilityInvokeEntry; import emu.grasscutter.net.proto.AttackResultOuterClass.AttackResult; import emu.grasscutter.net.proto.CombatInvokeEntryOuterClass.CombatInvokeEntry; +import emu.grasscutter.net.proto.GadgetInteractReqOuterClass.GadgetInteractReq; import emu.grasscutter.net.proto.InteractTypeOuterClass.InteractType; import emu.grasscutter.net.proto.MpSettingTypeOuterClass.MpSettingType; import emu.grasscutter.net.proto.OnlinePlayerInfoOuterClass.OnlinePlayerInfo; @@ -94,6 +96,7 @@ public class Player { private Set flyCloakList; private Set costumeList; private Set unlockedForgingBlueprints; + private Set unlockedCombines; private List activeForges; private Integer widgetId; @@ -158,11 +161,13 @@ public class Player { @Transient private MapMarksManager mapMarksManager; @Transient private StaminaManager staminaManager; @Transient private EnergyManager energyManager; + @Transient private ResinManager resinManager; @Transient private ForgingManager forgingManager; @Transient private DeforestationManager deforestationManager; private long springLastUsed; private HashMap mapMarks; + private int nextResinRefresh; @Deprecated @SuppressWarnings({"rawtypes", "unchecked"}) // Morphia only! @@ -193,6 +198,7 @@ public class Player { this.costumeList = new HashSet<>(); this.towerData = new TowerData(); this.unlockedForgingBlueprints = new HashSet<>(); + this.unlockedCombines = new HashSet<>(); this.activeForges = new ArrayList<>(); this.setSceneId(3); @@ -217,6 +223,7 @@ public class Player { this.staminaManager = new StaminaManager(this); this.sotsManager = new SotSManager(this); this.energyManager = new EnergyManager(this); + this.resinManager = new ResinManager(this); this.forgingManager = new ForgingManager(this); } @@ -248,6 +255,7 @@ public class Player { this.staminaManager = new StaminaManager(this); this.sotsManager = new SotSManager(this); this.energyManager = new EnergyManager(this); + this.resinManager = new ResinManager(this); this.deforestationManager = new DeforestationManager(this); this.forgingManager = new ForgingManager(this); } @@ -540,6 +548,10 @@ public class Player { return this.unlockedForgingBlueprints; } + public Set getUnlockedCombines() { + return this.unlockedCombines; + } + public List getActiveForges() { return this.activeForges; } @@ -648,6 +660,14 @@ public class Player { springLastUsed = val; } + public int getNextResinRefresh() { + return nextResinRefresh; + } + + public void setNextResinRefresh(int value) { + this.nextResinRefresh = value; + } + public SceneLoadState getSceneLoadState() { return sceneState; } @@ -940,7 +960,7 @@ public class Player { return this.getMailHandler().replaceMailByIndex(index, message); } - public void interactWith(int gadgetEntityId) { + public void interactWith(int gadgetEntityId, GadgetInteractReq request) { GameEntity entity = getScene().getEntityById(gadgetEntityId); if (entity == null) { return; @@ -970,7 +990,7 @@ public class Player { } else if (entity instanceof EntityGadget gadget) { if (gadget.getGadgetData().getType() == EntityType.RewardStatue) { if (scene.getChallenge() != null) { - scene.getChallenge().getStatueDrops(this); + scene.getChallenge().getStatueDrops(this, request); } this.sendPacket(new PacketGadgetInteractRsp(gadget, InteractType.INTERACT_TYPE_OPEN_STATUE)); } @@ -1142,6 +1162,10 @@ public class Player { return this.energyManager; } + public ResinManager getResinManager() { + return this.resinManager; + } + public ForgingManager getForgingManager() { return this.forgingManager; } @@ -1208,6 +1232,9 @@ public class Player { // Send updated forge queue data, if necessary. this.getForgingManager().sendPlayerForgingUpdate(); + + // Recharge resin. + this.getResinManager().rechargeResin(); } @@ -1291,7 +1318,9 @@ public class Player { session.send(new PacketWidgetGadgetAllDataNotify()); session.send(new PacketPlayerHomeCompInfoNotify(this)); session.send(new PacketHomeComfortInfoNotify(this)); + session.send(new PacketCombineDataNotify(this.unlockedCombines)); this.forgingManager.sendForgeDataNotify(); + this.resinManager.onPlayerLogin(); getTodayMoonCard(); // The timer works at 0:0, some users log in after that, use this method to check if they have received a reward today or not. If not, send the reward. diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerGadgetInteractReq.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerGadgetInteractReq.java index 6c8b6515a..9fa116977 100644 --- a/src/main/java/emu/grasscutter/server/packet/recv/HandlerGadgetInteractReq.java +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerGadgetInteractReq.java @@ -13,7 +13,7 @@ public class HandlerGadgetInteractReq extends PacketHandler { public void handle(GameSession session, byte[] header, byte[] payload) throws Exception { GadgetInteractReq req = GadgetInteractReq.parseFrom(payload); - session.getPlayer().interactWith(req.getGadgetEntityId()); + session.getPlayer().interactWith(req.getGadgetEntityId(), req); } } diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketCombineDataNotify.java b/src/main/java/emu/grasscutter/server/packet/send/PacketCombineDataNotify.java new file mode 100644 index 000000000..f7e4dabe5 --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketCombineDataNotify.java @@ -0,0 +1,18 @@ +package emu.grasscutter.server.packet.send; + +import emu.grasscutter.net.packet.BasePacket; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.CombineDataNotifyOuterClass.CombineDataNotify; + +public class PacketCombineDataNotify extends BasePacket { + + public PacketCombineDataNotify(Iterable unlockedCombines) { + super(PacketOpcodes.CombineDataNotify); + + CombineDataNotify proto = CombineDataNotify.newBuilder() + .addAllCombineIdList(unlockedCombines) + .build(); + + this.setData(proto); + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketCombineFormulaDataNotify.java b/src/main/java/emu/grasscutter/server/packet/send/PacketCombineFormulaDataNotify.java new file mode 100644 index 000000000..747110137 --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketCombineFormulaDataNotify.java @@ -0,0 +1,19 @@ +package emu.grasscutter.server.packet.send; + +import emu.grasscutter.net.packet.BasePacket; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.CombineFormulaDataNotifyOuterClass.CombineFormulaDataNotify; + +public class PacketCombineFormulaDataNotify extends BasePacket { + + public PacketCombineFormulaDataNotify(int combineId) { + super(PacketOpcodes.CombineFormulaDataNotify); + + CombineFormulaDataNotify proto = CombineFormulaDataNotify.newBuilder() + .setCombineId(combineId) + .setIsLocked(false) + .build(); + + this.setData(proto); + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketResinChangeNotify.java b/src/main/java/emu/grasscutter/server/packet/send/PacketResinChangeNotify.java new file mode 100644 index 000000000..b61c1ab0a --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketResinChangeNotify.java @@ -0,0 +1,23 @@ +package emu.grasscutter.server.packet.send; + +import emu.grasscutter.game.player.Player; +import emu.grasscutter.game.props.PlayerProperty; +import emu.grasscutter.net.packet.BasePacket; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.ResinChangeNotifyOuterClass.ResinChangeNotify; + +public class PacketResinChangeNotify extends BasePacket { + + public PacketResinChangeNotify(Player player) { + super(PacketOpcodes.ResinChangeNotify); + + ResinChangeNotify proto = ResinChangeNotify.newBuilder() + .setCurValue(player.getProperty(PlayerProperty.PROP_PLAYER_RESIN)) + .setNextAddTimestamp(player.getNextResinRefresh()) + .build(); + + // ToDo: Add ability to buy resin with primogems, has to be included here. + + this.setData(proto); + } +} diff --git a/src/main/java/emu/grasscutter/utils/ConfigContainer.java b/src/main/java/emu/grasscutter/utils/ConfigContainer.java index a61f5f42e..67496d765 100644 --- a/src/main/java/emu/grasscutter/utils/ConfigContainer.java +++ b/src/main/java/emu/grasscutter/utils/ConfigContainer.java @@ -177,6 +177,7 @@ public class ConfigContainer { public boolean enableShopItems = true; public boolean staminaUsage = true; public boolean energyUsage = false; + public ResinOptions resinOptions = new ResinOptions(); public Rates rates = new Rates(); public static class InventoryLimits { @@ -197,6 +198,12 @@ public class ConfigContainer { public float mora = 1.0f; public float leyLines = 1.0f; } + + public static class ResinOptions { + public boolean resinUsage = false; + public int cap = 160; + public int rechargeTime = 480; + } } public static class JoinOptions {