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
This commit is contained in:
ImmuState 2022-06-14 10:14:15 +02:00 committed by GitHub
parent 2fd2fab54c
commit 6e955c9fdb
13 changed files with 370 additions and 61 deletions

View File

@ -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) {

View File

@ -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<ItemParamData> 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(),

View File

@ -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<GameItem> rollRewards() {
List<GameItem> 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<GameItem> 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));

View File

@ -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<GameItem> {
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<GameItem> {
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<GameItem> {
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);
}

View File

@ -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<ItemParamData> 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());

View File

@ -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<ShopChestTable> shopChestTableList = player.getServer().getShopManager().getShopChestData();

View File

@ -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));
}
}

View File

@ -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<Integer> flyCloakList;
private Set<Integer> costumeList;
private Set<Integer> unlockedForgingBlueprints;
private Set<Integer> unlockedCombines;
private List<ActiveForgeData> 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<String, MapMark> 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<Integer> getUnlockedCombines() {
return this.unlockedCombines;
}
public List<ActiveForgeData> 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.

View File

@ -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);
}
}

View File

@ -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<Integer> unlockedCombines) {
super(PacketOpcodes.CombineDataNotify);
CombineDataNotify proto = CombineDataNotify.newBuilder()
.addAllCombineIdList(unlockedCombines)
.build();
this.setData(proto);
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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 {