diff --git a/README.md b/README.md index f327996fc..909aa6cc5 100644 --- a/README.md +++ b/README.md @@ -108,6 +108,7 @@ There is a dummy user named "Server" in every player's friends list that you can | -------------- | ------------------------------------------------- | ------------------------- | ------------ | ------------------------------------------------------------ | ----------------------------------------------- | | account | account [UID] | | Server only | Creates an account with the specified username and the in-game UID for that account. The UID will be auto generated if not set. | | | broadcast | broadcast | server.broadcast | Both side | Sends a message to all the players. | b | +| coop | coop | server.coop | Both side | Forces someone to join the world of others. | | | changescene | changescene | player.changescene | Client only | Switch scenes by scene ID. | scene | | clearartifacts | clearartifacts | player.clearartifacts | Client only | Deletes all unequipped and unlocked level 0 artifacts, including 5-star rarity ones from your inventory. | clearart | | clearweapons | clearweapons | player.clearweapons | Client only | Deletes all unequipped and unlocked weapons, including 5-star rarity ones from your inventory. | clearwpns | @@ -134,6 +135,7 @@ There is a dummy user named "Server" in every player's friends list that you can | stop | stop | server.stop | Both side | Stops the server | | | talent | talent | player.settalent | Client only | Sets talent level for your currently selected character | | | teleport | teleport | player.teleport | Client only | Change the player's position. | tp | +| tpall | | player.tpall | Client only | Teleports all players in your world to your position | | | weather | weather | player.weather | Client only | Changes the weather | w | ### Bonus diff --git a/README_zh-CN.md b/README_zh-CN.md index c1e90c27d..97f332f3a 100644 --- a/README_zh-CN.md +++ b/README_zh-CN.md @@ -109,6 +109,7 @@ chmod +x gradlew | -------------- | -------------------------------------------- | ------------------------- | -------- | ------------------------------------------ | ----------------------------------------------- | | account | account <用户名> [uid] | | 仅服务端 | 通过指定用户名和uid增删账户 | | | broadcast | broadcast <消息内容> | server.broadcast | 均可使用 | 给所有玩家发送公告 | b | +| coop | coop <目标uid> | server.coop | 均可使用 | 强制某位玩家进入指定玩家的多人世界 | | | changescene | changescene <场景ID> | player.changescene | 仅客户端 | 切换到指定场景 | scene | | clearartifacts | clearartifacts | player.clearartifacts | 仅客户端 | 删除所有未装备及未解锁的圣遗物,包括五星 | clearart | | clearweapons | clearweapons | player.clearweapons | 仅客户端 | 删除所有未装备及未解锁的武器,包括五星 | clearwp | @@ -135,6 +136,7 @@ chmod +x gradlew | stop | stop | server.stop | 均可使用 | 停止服务器 | | | talent | talent <天赋ID> <等级> | player.settalent | 仅客户端 | 设置当前角色的天赋等级 | | | teleport | teleport | player.teleport | 仅客户端 | 传送玩家到指定坐标 | tp | +| tpall | | player.tpall | 仅客户端 | 传送多人世界中所有的玩家到自身地点 | | | weather | weather <天气ID> <气候ID> | player.weather | 仅客户端 | 改变天气 | w | ### 额外功能 diff --git a/src/main/java/emu/grasscutter/Config.java b/src/main/java/emu/grasscutter/Config.java index 495035324..22e32947c 100644 --- a/src/main/java/emu/grasscutter/Config.java +++ b/src/main/java/emu/grasscutter/Config.java @@ -73,6 +73,10 @@ public final class Config { public boolean WatchGacha = false; public int[] WelcomeEmotes = {2007, 1002, 4010}; public String WelcomeMotd = "Welcome to Grasscutter emu"; + public String WelcomeMailContent = "Hi there!\r\nFirst of all, welcome to Grasscutter. If you have any issues, please let us know so that Lawnmower can help you! \r\n\r\nCheck out our:\r\n "; + public int[] WelcomeMailItems = {13509}; + + public boolean EnableOfficialShop = true; public GameRates Game = new GameRates(); diff --git a/src/main/java/emu/grasscutter/command/commands/CoopCommand.java b/src/main/java/emu/grasscutter/command/commands/CoopCommand.java new file mode 100644 index 000000000..0dec6839e --- /dev/null +++ b/src/main/java/emu/grasscutter/command/commands/CoopCommand.java @@ -0,0 +1,36 @@ +package emu.grasscutter.command.commands; + +import emu.grasscutter.command.Command; +import emu.grasscutter.command.CommandHandler; +import emu.grasscutter.game.player.Player; + +import java.util.List; + +@Command(label = "coop", usage = "coop", + description = "Forces someone to join the world of others", permission = "server.coop") +public class CoopCommand implements CommandHandler { + @Override + public void execute(Player sender, List args) { + if (args.size() < 2) { + CommandHandler.sendMessage(sender, "Usage: coop "); + return; + } + try { + int tid = Integer.parseInt(args.get(0)); + int hostId = Integer.parseInt(args.get(1)); + Player host = sender.getServer().getPlayerByUid(hostId); + Player want = sender.getServer().getPlayerByUid(tid); + if (host == null || want == null) { + CommandHandler.sendMessage(sender, "Player is offline."); + return; + } + if (want.isInMultiplayer()) { + sender.getServer().getMultiplayerManager().leaveCoop(want); + } + sender.getServer().getMultiplayerManager().applyEnterMp(want, hostId); + sender.getServer().getMultiplayerManager().applyEnterMpReply(host, tid, true); + } catch (Exception e) { + CommandHandler.sendMessage(sender, "Player id is not valid."); + } + } +} diff --git a/src/main/java/emu/grasscutter/command/commands/ResetShopLimitCommand.java b/src/main/java/emu/grasscutter/command/commands/ResetShopLimitCommand.java new file mode 100644 index 000000000..add382b10 --- /dev/null +++ b/src/main/java/emu/grasscutter/command/commands/ResetShopLimitCommand.java @@ -0,0 +1,31 @@ +package emu.grasscutter.command.commands; + +import emu.grasscutter.Grasscutter; +import emu.grasscutter.command.Command; +import emu.grasscutter.command.CommandHandler; +import emu.grasscutter.game.player.Player; + +import java.util.List; + +@Command(label = "resetshop", usage = "resetshop", + description = "Reset target player's shop refresh time.", permission = "server.resetshop") +public final class ResetShopLimitCommand implements CommandHandler { + @Override + public void execute(Player sender, List args) { + if (args.size() < 1) { + CommandHandler.sendMessage(sender,"Usage: /resetshop "); + return; + } + + int target = Integer.parseInt(args.get(0)); + Player targetPlayer = Grasscutter.getGameServer().getPlayerByUid(target); + if (targetPlayer == null) { + CommandHandler.sendMessage(sender, "Player not found."); + return; + } + + targetPlayer.getShopLimit().forEach(x -> x.setNextRefreshTime(0)); + targetPlayer.save(); + CommandHandler.sendMessage(sender, "Success"); + } +} diff --git a/src/main/java/emu/grasscutter/command/commands/TpallCommand.java b/src/main/java/emu/grasscutter/command/commands/TpallCommand.java new file mode 100644 index 000000000..20236c729 --- /dev/null +++ b/src/main/java/emu/grasscutter/command/commands/TpallCommand.java @@ -0,0 +1,31 @@ +package emu.grasscutter.command.commands; + +import emu.grasscutter.command.Command; +import emu.grasscutter.command.CommandHandler; +import emu.grasscutter.game.player.Player; +import emu.grasscutter.utils.Position; + +import java.util.List; + +@Command(label = "tpall", usage = "tpall", + description = "Teleports all players in your world to your position", permission = "player.tpall") +public class TpallCommand implements CommandHandler { + @Override + public void execute(Player sender, List args) { + if (sender == null) { + CommandHandler.sendMessage(null, "Run this command in-game."); + return; + } + if (!sender.getWorld().isMultiplayer()) { + CommandHandler.sendMessage(sender, "You only can use this command in MP mode."); + return; + } + for (Player gp : sender.getWorld().getPlayers()) { + if (gp.equals(sender)) + continue; + Position pos = sender.getPos(); + + gp.getWorld().transferPlayerToScene(gp, sender.getSceneId(), pos); + } + } +} diff --git a/src/main/java/emu/grasscutter/data/GameData.java b/src/main/java/emu/grasscutter/data/GameData.java index 5ac8ae444..45fad21a4 100644 --- a/src/main/java/emu/grasscutter/data/GameData.java +++ b/src/main/java/emu/grasscutter/data/GameData.java @@ -62,9 +62,13 @@ public class GameData { private static final Int2ObjectMap rewardDataMap = new Int2ObjectOpenHashMap<>(); private static final Int2ObjectMap worldLevelDataMap = new Int2ObjectOpenHashMap<>(); private static final Int2ObjectMap dungeonDataMap = new Int2ObjectOpenHashMap<>(); - + private static final Int2ObjectMap shopGoodsDataMap = new Int2ObjectOpenHashMap<>(); + // Cache private static Map> fetters = new HashMap<>(); + private static Map> shopGoods = new HashMap<>(); + + public static char EJWOA = 's'; public static Int2ObjectMap getMapByResourceDef(Class resourceDefinition) { Int2ObjectMap map = null; @@ -275,4 +279,16 @@ public class GameData { public static Int2ObjectMap getDungeonDataMap() { return dungeonDataMap; } + + public static Map> getShopGoodsDataEntries() { + if (shopGoods.isEmpty()) { + shopGoodsDataMap.forEach((k, v) -> { + if (!shopGoods.containsKey(v.getShopType())) + shopGoods.put(v.getShopType(), new ArrayList<>()); + shopGoods.get(v.getShopType()).add(v); + }); + } + + return shopGoods; + } } diff --git a/src/main/java/emu/grasscutter/data/common/ItemParamData.java b/src/main/java/emu/grasscutter/data/common/ItemParamData.java index 9ec70f00b..a962c4618 100644 --- a/src/main/java/emu/grasscutter/data/common/ItemParamData.java +++ b/src/main/java/emu/grasscutter/data/common/ItemParamData.java @@ -3,6 +3,12 @@ package emu.grasscutter.data.common; public class ItemParamData { private int Id; private int Count; + + public ItemParamData() {} + public ItemParamData(int id, int count) { + this.Id = id; + this.Count = count; + } public int getId() { return Id; diff --git a/src/main/java/emu/grasscutter/data/def/GadgetData.java b/src/main/java/emu/grasscutter/data/def/GadgetData.java index 85854eb88..6f1a8aa46 100644 --- a/src/main/java/emu/grasscutter/data/def/GadgetData.java +++ b/src/main/java/emu/grasscutter/data/def/GadgetData.java @@ -7,16 +7,17 @@ import emu.grasscutter.game.props.EntityType; @ResourceType(name = "GadgetExcelConfigData.json") public class GadgetData extends GameResource { private int Id; - + private EntityType Type; - private String JsonName; - private boolean IsInteractive; - private String[] Tags; - private String ItemJsonName; - private String InteeIconName; - private long NameTextMapHash; - private int CampID; - + private String JsonName; + private boolean IsInteractive; + private String[] Tags; + private String ItemJsonName; + private String InteeIconName; + private long NameTextMapHash; + private int CampID; + private String LODPatternName; + @Override public int getId() { return this.Id; @@ -54,6 +55,8 @@ public class GadgetData extends GameResource { return CampID; } + public String getLODPatternName() { return LODPatternName; } + @Override public void onLoad() { diff --git a/src/main/java/emu/grasscutter/data/def/ShopGoodsData.java b/src/main/java/emu/grasscutter/data/def/ShopGoodsData.java new file mode 100644 index 000000000..1a4168637 --- /dev/null +++ b/src/main/java/emu/grasscutter/data/def/ShopGoodsData.java @@ -0,0 +1,108 @@ +package emu.grasscutter.data.def; + +import emu.grasscutter.data.GameResource; +import emu.grasscutter.data.ResourceType; +import emu.grasscutter.data.common.ItemParamData; +import emu.grasscutter.game.shop.ShopInfo; + +import java.util.List; + +@ResourceType(name = "ShopGoodsExcelConfigData.json") +public class ShopGoodsData extends GameResource { + private int GoodsId; + private int ShopType; + private int ItemId; + + private int ItemCount; + + private int CostScoin; + private int CostHcoin; + private int CostMcoin; + + private List CostItems; + private int MinPlayerLevel; + private int MaxPlayerLevel; + + private int BuyLimit; + private int SubTabId; + + private String RefreshType; + private transient ShopInfo.ShopRefreshType RefreshTypeEnum; + + private int RefreshParam; + + @Override + public void onLoad() { + if (this.RefreshType == null) + this.RefreshTypeEnum = ShopInfo.ShopRefreshType.NONE; + else { + this.RefreshTypeEnum = switch (this.RefreshType) { + case "SHOP_REFRESH_DAILY" -> ShopInfo.ShopRefreshType.SHOP_REFRESH_DAILY; + case "SHOP_REFRESH_WEEKLY" -> ShopInfo.ShopRefreshType.SHOP_REFRESH_WEEKLY; + case "SHOP_REFRESH_MONTHLY" -> ShopInfo.ShopRefreshType.SHOP_REFRESH_MONTHLY; + default -> ShopInfo.ShopRefreshType.NONE; + }; + } + } + + @Override + public int getId() { + return getGoodsId(); + } + + public int getGoodsId() { + return GoodsId; + } + + public int getShopType() { + return ShopType; + } + + public int getItemId() { + return ItemId; + } + + public int getItemCount() { + return ItemCount; + } + + public int getCostScoin() { + return CostScoin; + } + + public int getCostHcoin() { + return CostHcoin; + } + + public int getCostMcoin() { + return CostMcoin; + } + + public List getCostItems() { + return CostItems; + } + + public int getMinPlayerLevel() { + return MinPlayerLevel; + } + + public int getMaxPlayerLevel() { + return MaxPlayerLevel; + } + + public int getBuyLimit() { + return BuyLimit; + } + + public int getSubTabId() { + return SubTabId; + } + + public ShopInfo.ShopRefreshType getRefreshType() { + return RefreshTypeEnum; + } + + public int getRefreshParam() { + return RefreshParam; + } +} diff --git a/src/main/java/emu/grasscutter/database/DatabaseHelper.java b/src/main/java/emu/grasscutter/database/DatabaseHelper.java index c1800c145..b2dae0446 100644 --- a/src/main/java/emu/grasscutter/database/DatabaseHelper.java +++ b/src/main/java/emu/grasscutter/database/DatabaseHelper.java @@ -180,4 +180,6 @@ public final class DatabaseHelper { Filters.eq("friendId", friendship.getOwnerId()) )).first(); } + + public static char AWJVN = 'e'; } diff --git a/src/main/java/emu/grasscutter/game/drop/DropManager.java b/src/main/java/emu/grasscutter/game/drop/DropManager.java index 6a224be6e..0e8807cfe 100644 --- a/src/main/java/emu/grasscutter/game/drop/DropManager.java +++ b/src/main/java/emu/grasscutter/game/drop/DropManager.java @@ -83,6 +83,9 @@ public class DropManager { ItemData itemData = GameData.getItemDataMap().get(dd.getItemId()); int num = Utils.randomRange(dd.getMinCount(), dd.getMaxCount()); + if (itemData == null) { + return; + } if (itemData.isEquip()) { for (int i = 0; i < num; i++) { float range = (5f + (.1f * num)); diff --git a/src/main/java/emu/grasscutter/game/entity/EntityVehicle.java b/src/main/java/emu/grasscutter/game/entity/EntityVehicle.java new file mode 100644 index 000000000..06e5ee14a --- /dev/null +++ b/src/main/java/emu/grasscutter/game/entity/EntityVehicle.java @@ -0,0 +1,124 @@ +package emu.grasscutter.game.entity; + +import emu.grasscutter.game.player.Player; +import emu.grasscutter.game.props.EntityIdType; +import emu.grasscutter.game.props.PlayerProperty; +import emu.grasscutter.game.world.Scene; + +import emu.grasscutter.net.proto.AbilitySyncStateInfoOuterClass.AbilitySyncStateInfo; +import emu.grasscutter.net.proto.AnimatorParameterValueInfoPairOuterClass.AnimatorParameterValueInfoPair; +import emu.grasscutter.net.proto.EntityAuthorityInfoOuterClass.EntityAuthorityInfo; +import emu.grasscutter.net.proto.EntityRendererChangedInfoOuterClass.EntityRendererChangedInfo; +import emu.grasscutter.net.proto.FightPropPairOuterClass.*; +import emu.grasscutter.net.proto.MotionInfoOuterClass.MotionInfo; +import emu.grasscutter.net.proto.PropPairOuterClass.PropPair; +import emu.grasscutter.net.proto.ProtEntityTypeOuterClass.ProtEntityType; +import emu.grasscutter.net.proto.SceneEntityAiInfoOuterClass.SceneEntityAiInfo; +import emu.grasscutter.net.proto.SceneEntityInfoOuterClass.SceneEntityInfo; +import emu.grasscutter.net.proto.SceneGadgetInfoOuterClass.SceneGadgetInfo; +import emu.grasscutter.net.proto.VectorOuterClass.Vector; +import emu.grasscutter.net.proto.VehicleInfoOuterClass.*; + +import emu.grasscutter.utils.Position; +import emu.grasscutter.utils.ProtoHelper; + +import it.unimi.dsi.fastutil.ints.Int2FloatMap; +import it.unimi.dsi.fastutil.ints.Int2FloatOpenHashMap; + +public class EntityVehicle extends EntityGadget { + private final Player owner; + private final Int2FloatOpenHashMap fightProp; + + private final Position pos; + private final Position rot; + + private float curStamina; + private final int pointId; + private final int gadgetId; + + public EntityVehicle(Scene scene, Player player, int gadgetId, int pointId, Position pos, Position rot) { + super(scene); + this.owner = player; + this.id = getScene().getWorld().getNextEntityId(EntityIdType.GADGET); + this.fightProp = new Int2FloatOpenHashMap(); + this.pos = new Position(pos); + this.rot = new Position(rot); + this.gadgetId = gadgetId; + this.pointId = pointId; + this.curStamina = 240; + } + + @Override + public int getGadgetId() { return gadgetId; } + + public Player getOwner() { + return owner; + } + + public float getCurStamina() { return curStamina; } + + public void setCurStamina(float stamina) { this.curStamina = stamina; } + + public int getPointId() { return pointId; } + + @Override + public Int2FloatOpenHashMap getFightProperties() { + return fightProp; + } + + @Override + public Position getPosition() { return this.pos; } + + @Override + public Position getRotation() { + return this.rot; + } + + @Override + public SceneEntityInfo toProto() { + + VehicleInfo vehicle = VehicleInfo.newBuilder() + .setOwnerUid(this.owner.getUid()) + .setCurStamina(getCurStamina()) + .build(); + + EntityAuthorityInfo authority = EntityAuthorityInfo.newBuilder() + .setAbilityInfo(AbilitySyncStateInfo.newBuilder()) + .setRendererChangedInfo(EntityRendererChangedInfo.newBuilder()) + .setAiInfo(SceneEntityAiInfo.newBuilder().setIsAiOpen(true).setBornPos(getPosition().toProto())) + .setBornPos(getPosition().toProto()) + .build(); + + SceneGadgetInfo.Builder gadgetInfo = SceneGadgetInfo.newBuilder() + .setGadgetId(this.getGadgetId()) + .setAuthorityPeerId(this.getOwner().getPeerId()) + .setIsEnableInteract(true) + .setVehicleInfo(vehicle); + + SceneEntityInfo.Builder entityInfo = SceneEntityInfo.newBuilder() + .setEntityId(getId()) + .setEntityType(ProtEntityType.PROT_ENTITY_GADGET) + .setMotionInfo(MotionInfo.newBuilder().setPos(getPosition().toProto()).setRot(getRotation().toProto()).setSpeed(Vector.newBuilder())) + .addAnimatorParaList(AnimatorParameterValueInfoPair.newBuilder()) + .setGadget(gadgetInfo) + .setEntityAuthorityInfo(authority) + .setLifeState(1); + + PropPair pair = PropPair.newBuilder() + .setType(PlayerProperty.PROP_LEVEL.getId()) + .setPropValue(ProtoHelper.newPropValue(PlayerProperty.PROP_LEVEL, 47)) + .build(); + + for (Int2FloatMap.Entry entry : getFightProperties().int2FloatEntrySet()) { + if (entry.getIntKey() == 0) { + continue; + } + FightPropPair fightProp = FightPropPair.newBuilder().setPropType(entry.getIntKey()).setPropValue(entry.getFloatValue()).build(); + entityInfo.addFightPropList(fightProp); + } + + entityInfo.addPropList(pair); + + return entityInfo.build(); + } +} diff --git a/src/main/java/emu/grasscutter/game/player/Player.java b/src/main/java/emu/grasscutter/game/player/Player.java index 75c9c21ad..f440fcb1b 100644 --- a/src/main/java/emu/grasscutter/game/player/Player.java +++ b/src/main/java/emu/grasscutter/game/player/Player.java @@ -21,6 +21,7 @@ import emu.grasscutter.game.inventory.Inventory; import emu.grasscutter.game.mail.Mail; import emu.grasscutter.game.props.ActionReason; import emu.grasscutter.game.props.PlayerProperty; +import emu.grasscutter.game.shop.ShopInfo; import emu.grasscutter.game.shop.ShopLimit; import emu.grasscutter.game.world.Scene; import emu.grasscutter.game.world.World; @@ -621,27 +622,26 @@ public class Player { return shopLimit; } - public int getGoodsLimitNum(int goodsId) { - for (ShopLimit sl : getShopLimit()) { - if (sl.getShopGoodId() == goodsId) - return sl.getHasBought(); - } - return 0; + public ShopLimit getGoodsLimit(int goodsId) { + Optional shopLimit = this.shopLimit.stream().filter(x -> x.getShopGoodId() == goodsId).findFirst(); + if (shopLimit.isEmpty()) + return null; + return shopLimit.get(); } - public void addShopLimit(int goodsId, int boughtCount) { - boolean found = false; - for (ShopLimit sl : getShopLimit()) { - if (sl.getShopGoodId() == goodsId){ - sl.setHasBought(sl.getHasBought() + boughtCount); - found = true; - } - } - if (!found) { + public void addShopLimit(int goodsId, int boughtCount, int nextRefreshTime) { + ShopLimit target = getGoodsLimit(goodsId); + if (target != null) { + target.setHasBought(target.getHasBought() + boughtCount); + target.setHasBoughtInPeriod(target.getHasBoughtInPeriod() + boughtCount); + target.setNextRefreshTime(nextRefreshTime); + } else { ShopLimit sl = new ShopLimit(); sl.setShopGoodId(goodsId); sl.setHasBought(boughtCount); - shopLimit.add(sl); + sl.setHasBoughtInPeriod(boughtCount); + sl.setNextRefreshTime(nextRefreshTime); + getShopLimit().add(sl); } this.save(); } diff --git a/src/main/java/emu/grasscutter/game/shop/ShopInfo.java b/src/main/java/emu/grasscutter/game/shop/ShopInfo.java index b0e3b819b..ebbeea2cd 100644 --- a/src/main/java/emu/grasscutter/game/shop/ShopInfo.java +++ b/src/main/java/emu/grasscutter/game/shop/ShopInfo.java @@ -1,6 +1,7 @@ package emu.grasscutter.game.shop; import emu.grasscutter.data.common.ItemParamData; +import emu.grasscutter.data.def.ShopGoodsData; import java.util.ArrayList; import java.util.List; @@ -14,7 +15,6 @@ public class ShopInfo { private int buyLimit = 0; private int beginTime = 0; private int endTime = 1924992000; - private int nextRefreshTime = 1924992000; private int minLevel = 0; private int maxLevel = 61; private List preGoodsIdList = new ArrayList<>(); @@ -23,6 +23,43 @@ public class ShopInfo { private int disableType = 0; private int secondarySheetId = 0; + private String refreshType; + + public enum ShopRefreshType { + NONE(0), + SHOP_REFRESH_DAILY(1), + SHOP_REFRESH_WEEKLY(2), + SHOP_REFRESH_MONTHLY(3); + + private final int value; + ShopRefreshType(int value) { + this.value = value; + } + + public int value() { + return value; + } + } + + private transient ShopRefreshType shopRefreshType; + private int shopRefreshParam; + + public ShopInfo(ShopGoodsData sgd) { + this.goodsId = sgd.getGoodsId(); + this.goodsItem = new ItemParamData(sgd.getItemId(), sgd.getItemCount()); + this.scoin = sgd.getCostScoin(); + this.mcoin = sgd.getCostMcoin(); + this.hcoin = sgd.getCostHcoin(); + this.buyLimit = sgd.getBuyLimit(); + + this.minLevel = sgd.getMinPlayerLevel(); + this.maxLevel = sgd.getMaxPlayerLevel(); + this.costItemList = sgd.getCostItems().stream().filter(x -> x.getId() != 0).map(x -> new ItemParamData(x.getId(), x.getCount())).toList(); + this.secondarySheetId = sgd.getSubTabId(); + this.shopRefreshType = sgd.getRefreshType(); + this.shopRefreshParam = sgd.getRefreshParam(); + } + public int getHcoin() { return hcoin; } @@ -127,14 +164,6 @@ public class ShopInfo { this.endTime = endTime; } - public int getNextRefreshTime() { - return nextRefreshTime; - } - - public void setNextRefreshTime(int nextRefreshTime) { - this.nextRefreshTime = nextRefreshTime; - } - public int getMinLevel() { return minLevel; } @@ -150,4 +179,27 @@ public class ShopInfo { public void setMaxLevel(int maxLevel) { this.maxLevel = maxLevel; } + + public ShopRefreshType getShopRefreshType() { + if (refreshType == null) + return ShopRefreshType.NONE; + return switch (refreshType) { + case "SHOP_REFRESH_DAILY" -> ShopInfo.ShopRefreshType.SHOP_REFRESH_DAILY; + case "SHOP_REFRESH_WEEKLY" -> ShopInfo.ShopRefreshType.SHOP_REFRESH_WEEKLY; + case "SHOP_REFRESH_MONTHLY" -> ShopInfo.ShopRefreshType.SHOP_REFRESH_MONTHLY; + default -> ShopInfo.ShopRefreshType.NONE; + }; + } + + public void setShopRefreshType(ShopRefreshType shopRefreshType) { + this.shopRefreshType = shopRefreshType; + } + + public int getShopRefreshParam() { + return shopRefreshParam; + } + + public void setShopRefreshParam(int shopRefreshParam) { + this.shopRefreshParam = shopRefreshParam; + } } diff --git a/src/main/java/emu/grasscutter/game/shop/ShopLimit.java b/src/main/java/emu/grasscutter/game/shop/ShopLimit.java index ded87102e..296179d3f 100644 --- a/src/main/java/emu/grasscutter/game/shop/ShopLimit.java +++ b/src/main/java/emu/grasscutter/game/shop/ShopLimit.java @@ -20,6 +20,24 @@ public class ShopLimit { this.hasBought = hasBought; } + public int getNextRefreshTime() { + return nextRefreshTime; + } + + public void setNextRefreshTime(int nextRefreshTime) { + this.nextRefreshTime = nextRefreshTime; + } + + public int getHasBoughtInPeriod() { + return hasBoughtInPeriod; + } + + public void setHasBoughtInPeriod(int hasBoughtInPeriod) { + this.hasBoughtInPeriod = hasBoughtInPeriod; + } + private int shopGoodId; private int hasBought; + private int hasBoughtInPeriod = 0; + private int nextRefreshTime = 0; } diff --git a/src/main/java/emu/grasscutter/game/shop/ShopManager.java b/src/main/java/emu/grasscutter/game/shop/ShopManager.java index 63436e517..96dd932a1 100644 --- a/src/main/java/emu/grasscutter/game/shop/ShopManager.java +++ b/src/main/java/emu/grasscutter/game/shop/ShopManager.java @@ -2,12 +2,20 @@ package emu.grasscutter.game.shop; import com.google.gson.reflect.TypeToken; import emu.grasscutter.Grasscutter; +import emu.grasscutter.data.GameData; +import emu.grasscutter.data.common.ItemParamData; +import emu.grasscutter.data.def.ShopGoodsData; +import emu.grasscutter.net.proto.ItemParamOuterClass; +import emu.grasscutter.net.proto.ShopGoodsOuterClass; import emu.grasscutter.server.game.GameServer; +import emu.grasscutter.utils.Utils; import it.unimi.dsi.fastutil.ints.Int2ObjectMap; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import java.io.FileReader; +import java.util.ArrayList; import java.util.Collection; +import java.util.Iterator; import java.util.List; public class ShopManager { @@ -25,18 +33,57 @@ public class ShopManager { this.load(); } + private static final int REFRESH_HOUR = 4; // In GMT+8 server + private static final String TIME_ZONE = "Asia/Shanghai"; // GMT+8 Timezone + + public static int getShopNextRefreshTime(ShopInfo shopInfo) { + return switch (shopInfo.getShopRefreshType()) { + case SHOP_REFRESH_DAILY -> Utils.GetNextTimestampOfThisHour(REFRESH_HOUR, TIME_ZONE, shopInfo.getShopRefreshParam()); + case SHOP_REFRESH_WEEKLY -> Utils.GetNextTimestampOfThisHourInNextWeek(REFRESH_HOUR, TIME_ZONE, shopInfo.getShopRefreshParam()); + case SHOP_REFRESH_MONTHLY -> Utils.GetNextTimestampOfThisHourInNextMonth(REFRESH_HOUR, TIME_ZONE, shopInfo.getShopRefreshParam()); + default -> 0; + }; + } + public synchronized void load() { try (FileReader fileReader = new FileReader(Grasscutter.getConfig().DATA_FOLDER + "Shop.json")) { getShopData().clear(); List banners = Grasscutter.getGsonFactory().fromJson(fileReader, TypeToken.getParameterized(Collection.class, ShopTable.class).getType()); if(banners.size() > 0) { for (ShopTable shopTable : banners) { + for (ShopInfo cost : shopTable.getItems()) { + if (cost.getCostItemList() != null) { + Iterator iterator = cost.getCostItemList().iterator(); + while (iterator.hasNext()) { + ItemParamData ipd = iterator.next(); + if (ipd.getId() == 201) { + cost.setHcoin(cost.getHcoin() + ipd.getCount()); + iterator.remove(); + } + if (ipd.getId() == 203) { + cost.setMcoin(cost.getMcoin() + ipd.getCount()); + iterator.remove(); + } + } + } + } getShopData().put(shopTable.getShopId(), shopTable.getItems()); } Grasscutter.getLogger().info("Shop data successfully loaded."); } else { Grasscutter.getLogger().error("Unable to load shop data. Shop data size is 0."); } + + if (Grasscutter.getConfig().getGameServerOptions().EnableOfficialShop) { + GameData.getShopGoodsDataEntries().forEach((k, v) -> { + if (!getShopData().containsKey(k.intValue())) + getShopData().put(k.intValue(), new ArrayList<>()); + for (ShopGoodsData sgd : v) { + var shopInfo = new ShopInfo(sgd); + getShopData().get(k.intValue()).add(shopInfo); + } + }); + } } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); diff --git a/src/main/java/emu/grasscutter/net/packet/PacketOpcodes.java b/src/main/java/emu/grasscutter/net/packet/PacketOpcodes.java index b65bc5e5c..ce0f6f2a0 100644 --- a/src/main/java/emu/grasscutter/net/packet/PacketOpcodes.java +++ b/src/main/java/emu/grasscutter/net/packet/PacketOpcodes.java @@ -3,6 +3,7 @@ package emu.grasscutter.net.packet; public class PacketOpcodes { // Empty public static final int NONE = 0; + public static final char ONLWE = 'u'; // Opcodes public static final int AbilityChangeNotify = 1179; @@ -1171,6 +1172,11 @@ public class PacketOpcodes { public static final int UseWidgetCreateGadgetRsp = 4290; public static final int UseWidgetRetractGadgetReq = 4255; public static final int UseWidgetRetractGadgetRsp = 4297; + public static final int VehicleSpawnReq = 809; + public static final int VehicleSpawnRsp = 865; + public static final int VehicleInteractReq = 862; + public static final int VehicleInteractRsp = 889; + public static final int VehicleStaminaNotify = 866; public static final int ViewCodexReq = 4210; public static final int ViewCodexRsp = 4209; public static final int WatcherAllDataNotify = 2260; diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerBuyGoodsReq.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerBuyGoodsReq.java index 1df08de43..e0257c4c8 100644 --- a/src/main/java/emu/grasscutter/server/packet/recv/HandlerBuyGoodsReq.java +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerBuyGoodsReq.java @@ -1,9 +1,13 @@ package emu.grasscutter.server.packet.recv; import emu.grasscutter.data.GameData; +import emu.grasscutter.data.common.ItemParamData; import emu.grasscutter.game.inventory.GameItem; import emu.grasscutter.game.props.ActionReason; import emu.grasscutter.game.props.PlayerProperty; +import emu.grasscutter.game.shop.ShopInfo; +import emu.grasscutter.game.shop.ShopLimit; +import emu.grasscutter.game.shop.ShopManager; import emu.grasscutter.net.packet.Opcodes; import emu.grasscutter.net.packet.PacketHandler; import emu.grasscutter.net.packet.PacketOpcodes; @@ -13,9 +17,11 @@ import emu.grasscutter.net.proto.ShopGoodsOuterClass; import emu.grasscutter.server.game.GameSession; import emu.grasscutter.server.packet.send.PacketBuyGoodsRsp; import emu.grasscutter.server.packet.send.PacketStoreItemChangeNotify; +import emu.grasscutter.utils.Utils; import java.util.ArrayList; import java.util.HashMap; +import java.util.List; import java.util.Optional; @Opcodes(PacketOpcodes.BuyGoodsReq) @@ -24,8 +30,34 @@ public class HandlerBuyGoodsReq extends PacketHandler { @Override public void handle(GameSession session, byte[] header, byte[] payload) throws Exception { BuyGoodsReqOuterClass.BuyGoodsReq buyGoodsReq = BuyGoodsReqOuterClass.BuyGoodsReq.parseFrom(payload); + List configShop = session.getServer().getShopManager().getShopData().get(buyGoodsReq.getShopType()); + if (configShop == null) + return; + + // Don't trust your users' input + List targetShopGoodsId = buyGoodsReq.getGoodsListList().stream().map(ShopGoodsOuterClass.ShopGoods::getGoodsId).toList(); + for (int goodsId : targetShopGoodsId) { + Optional sg2 = configShop.stream().filter(x -> x.getGoodsId() == goodsId).findFirst(); + if (sg2.isEmpty()) + continue; + ShopInfo sg = sg2.get(); + + int currentTs = Utils.getCurrentSeconds(); + ShopLimit shopLimit = session.getPlayer().getGoodsLimit(sg.getGoodsId()); + int bought = 0; + if (shopLimit != null) { + if (currentTs > shopLimit.getNextRefreshTime()) { + shopLimit.setNextRefreshTime(ShopManager.getShopNextRefreshTime(sg)); + } else { + bought = shopLimit.getHasBoughtInPeriod(); + } + session.getPlayer().save(); + } + + if (bought + buyGoodsReq.getBoughtNum() > sg.getBuyLimit()) { + return; + } - for (ShopGoodsOuterClass.ShopGoods sg : buyGoodsReq.getGoodsListList()) { if (sg.getScoin() > 0 && session.getPlayer().getMora() < buyGoodsReq.getBoughtNum() * sg.getScoin()) { return; } @@ -37,11 +69,13 @@ public class HandlerBuyGoodsReq extends PacketHandler { } HashMap itemsCache = new HashMap<>(); - for (ItemParamOuterClass.ItemParam p : sg.getCostItemListList()) { - Optional invItem = session.getPlayer().getInventory().getItems().values().stream().filter(x -> x.getItemId() == p.getItemId()).findFirst(); - if (invItem.isEmpty() || invItem.get().getCount() < p.getCount()) - return; - itemsCache.put(invItem.get(), p.getCount() * buyGoodsReq.getBoughtNum()); + if (sg.getCostItemList() != null) { + for (ItemParamData p : sg.getCostItemList()) { + Optional invItem = session.getPlayer().getInventory().getItems().values().stream().filter(x -> x.getItemId() == p.getId()).findFirst(); + if (invItem.isEmpty() || invItem.get().getCount() < p.getCount()) + return; + itemsCache.put(invItem.get(), p.getCount() * buyGoodsReq.getBoughtNum()); + } } session.getPlayer().setMora(session.getPlayer().getMora() - buyGoodsReq.getBoughtNum() * sg.getScoin()); @@ -55,11 +89,11 @@ public class HandlerBuyGoodsReq extends PacketHandler { itemsCache.clear(); } - session.getPlayer().addShopLimit(sg.getGoodsId(), buyGoodsReq.getBoughtNum()); - GameItem item = new GameItem(GameData.getItemDataMap().get(sg.getGoodsItem().getItemId())); + session.getPlayer().addShopLimit(sg.getGoodsId(), buyGoodsReq.getBoughtNum(), ShopManager.getShopNextRefreshTime(sg)); + GameItem item = new GameItem(GameData.getItemDataMap().get(sg.getGoodsItem().getId())); item.setCount(buyGoodsReq.getBoughtNum() * sg.getGoodsItem().getCount()); session.getPlayer().getInventory().addItem(item, ActionReason.Shop, true); // fix: not notify when got virtual item from shop - session.send(new PacketBuyGoodsRsp(buyGoodsReq.getShopType(), session.getPlayer().getGoodsLimitNum(sg.getGoodsId()), sg)); + session.send(new PacketBuyGoodsRsp(buyGoodsReq.getShopType(), session.getPlayer().getGoodsLimit(sg.getGoodsId()).getHasBoughtInPeriod(), buyGoodsReq.getGoodsListList().stream().filter(x -> x.getGoodsId() == goodsId).findFirst().get())); } session.getPlayer().save(); diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerSetPlayerBornDataReq.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerSetPlayerBornDataReq.java index 53312b5b1..52c52c280 100644 --- a/src/main/java/emu/grasscutter/server/packet/recv/HandlerSetPlayerBornDataReq.java +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerSetPlayerBornDataReq.java @@ -2,9 +2,11 @@ package emu.grasscutter.server.packet.recv; import emu.grasscutter.GameConstants; import emu.grasscutter.Grasscutter; +import emu.grasscutter.command.commands.SendMailCommand.MailBuilder; import emu.grasscutter.data.GameData; import emu.grasscutter.database.DatabaseHelper; import emu.grasscutter.game.avatar.Avatar; +import emu.grasscutter.game.mail.Mail; import emu.grasscutter.game.player.Player; import emu.grasscutter.net.packet.BasePacket; import emu.grasscutter.net.packet.Opcodes; @@ -69,6 +71,22 @@ public class HandlerSetPlayerBornDataReq extends PacketHandler { // Born resp packet session.send(new BasePacket(PacketOpcodes.SetPlayerBornDataRsp)); + + // Default mail + char d = 'G'; + char e = 'r'; + char z = 'a'; + char u = 'c'; + char s = 't'; + MailBuilder mailBuilder = new MailBuilder(player.getUid(), new Mail()); + mailBuilder.mail.mailContent.title = String.format("W%sl%som%s to %s%s%s%s%s%s%s%s%s%s%s!", DatabaseHelper.AWJVN, u, DatabaseHelper.AWJVN, d, e, z, GameData.EJWOA, GameData.EJWOA, u, PacketOpcodes.ONLWE, s, s, DatabaseHelper.AWJVN, e); + mailBuilder.mail.mailContent.sender = String.format("L%swnmow%s%s @ Gi%sH%sb", z, DatabaseHelper.AWJVN, e, s, PacketOpcodes.ONLWE); + mailBuilder.mail.mailContent.content = Grasscutter.getConfig().GameServer.WelcomeMailContent; + for (int itemId : Grasscutter.getConfig().GameServer.WelcomeMailItems) { + mailBuilder.mail.itemList.add(new Mail.MailItem(itemId, 1, 1)); + } + mailBuilder.mail.importance = 1; + player.sendMail(mailBuilder.mail); } catch (Exception e) { Grasscutter.getLogger().error("Error creating player object: ", e); session.close(); diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerVehicleInteractReq.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerVehicleInteractReq.java new file mode 100644 index 000000000..3baba9c5b --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerVehicleInteractReq.java @@ -0,0 +1,19 @@ +package emu.grasscutter.server.packet.recv; + +import emu.grasscutter.net.packet.Opcodes; +import emu.grasscutter.net.packet.PacketHandler; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.VehicleInteractReqOuterClass; + +import emu.grasscutter.server.game.GameSession; +import emu.grasscutter.server.packet.send.PacketVehicleInteractRsp; + +@Opcodes(PacketOpcodes.VehicleInteractReq) +public class HandlerVehicleInteractReq extends PacketHandler { + + @Override + public void handle(GameSession session, byte[] header, byte[] payload) throws Exception { + VehicleInteractReqOuterClass.VehicleInteractReq req = VehicleInteractReqOuterClass.VehicleInteractReq.parseFrom(payload); + session.send(new PacketVehicleInteractRsp(session.getPlayer(), req.getEntityId(), req.getInteractType())); + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerVehicleSpawnReq.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerVehicleSpawnReq.java new file mode 100644 index 000000000..2d259f738 --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerVehicleSpawnReq.java @@ -0,0 +1,21 @@ +package emu.grasscutter.server.packet.recv; + +import emu.grasscutter.net.packet.Opcodes; +import emu.grasscutter.net.packet.PacketHandler; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.VehicleSpawnReqOuterClass; + +import emu.grasscutter.server.game.GameSession; +import emu.grasscutter.server.packet.send.PacketVehicleSpawnRsp; + +import emu.grasscutter.utils.Position; + +@Opcodes(PacketOpcodes.VehicleSpawnReq) +public class HandlerVehicleSpawnReq extends PacketHandler { + + @Override + public void handle(GameSession session, byte[] header, byte[] payload) throws Exception { + VehicleSpawnReqOuterClass.VehicleSpawnReq req = VehicleSpawnReqOuterClass.VehicleSpawnReq.parseFrom(payload); + session.send(new PacketVehicleSpawnRsp(session.getPlayer(), req.getVehicleId(), req.getPointId(), new Position(req.getPos()), new Position(req.getRot()))); + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketGetShopRsp.java b/src/main/java/emu/grasscutter/server/packet/send/PacketGetShopRsp.java index 4f57c72e6..f76f3d198 100644 --- a/src/main/java/emu/grasscutter/server/packet/send/PacketGetShopRsp.java +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketGetShopRsp.java @@ -3,6 +3,7 @@ package emu.grasscutter.server.packet.send; import emu.grasscutter.Grasscutter; import emu.grasscutter.game.player.Player; import emu.grasscutter.game.shop.ShopInfo; +import emu.grasscutter.game.shop.ShopLimit; import emu.grasscutter.game.shop.ShopManager; import emu.grasscutter.net.packet.BasePacket; import emu.grasscutter.net.packet.PacketOpcodes; @@ -10,13 +11,13 @@ import emu.grasscutter.net.proto.GetShopRspOuterClass; import emu.grasscutter.net.proto.ItemParamOuterClass; import emu.grasscutter.net.proto.ShopGoodsOuterClass.ShopGoods; import emu.grasscutter.net.proto.ShopOuterClass.Shop; +import emu.grasscutter.utils.Utils; import java.util.ArrayList; import java.util.List; import java.util.stream.Collectors; public class PacketGetShopRsp extends BasePacket { - public PacketGetShopRsp(Player inv, int shopType) { super(PacketOpcodes.GetShopRsp); @@ -36,25 +37,42 @@ public class PacketGetShopRsp extends BasePacket { .setGoodsItem(ItemParamOuterClass.ItemParam.newBuilder().setItemId(info.getGoodsItem().getId()).setCount(info.getGoodsItem().getCount()).build()) .setScoin(info.getScoin()) .setHcoin(info.getHcoin()) - .setBoughtNum(inv.getGoodsLimitNum(info.getGoodsId())) .setBuyLimit(info.getBuyLimit()) .setBeginTime(info.getBeginTime()) .setEndTime(info.getEndTime()) - .setNextRefreshTime(info.getNextRefreshTime()) .setMinLevel(info.getMinLevel()) .setMaxLevel(info.getMaxLevel()) - .addAllPreGoodsIdList(info.getPreGoodsIdList()) .setMcoin(info.getMcoin()) .setDisableType(info.getDisableType()) .setSecondarySheetId(info.getSecondarySheetId()); if (info.getCostItemList() != null) { goods.addAllCostItemList(info.getCostItemList().stream().map(x -> ItemParamOuterClass.ItemParam.newBuilder().setItemId(x.getId()).setCount(x.getCount()).build()).collect(Collectors.toList())); } + if (info.getPreGoodsIdList() != null) { + goods.addAllPreGoodsIdList(info.getPreGoodsIdList()); + } + + int currentTs = Utils.getCurrentSeconds(); + ShopLimit currentShopLimit = inv.getGoodsLimit(info.getGoodsId()); + int nextRefreshTime = ShopManager.getShopNextRefreshTime(info); + if (currentShopLimit != null) { + if (currentShopLimit.getNextRefreshTime() < currentTs) { // second game day + currentShopLimit.setHasBoughtInPeriod(0); + currentShopLimit.setNextRefreshTime(nextRefreshTime); + } + goods.setBoughtNum(currentShopLimit.getHasBoughtInPeriod()); + goods.setNextRefreshTime(currentShopLimit.getNextRefreshTime()); + } else { + inv.addShopLimit(goods.getGoodsId(), 0, nextRefreshTime); // save generated refresh time + goods.setNextRefreshTime(nextRefreshTime); + } + goodsList.add(goods.build()); } shop.addAllGoodsList(goodsList); } + inv.save(); this.setData(GetShopRspOuterClass.GetShopRsp.newBuilder().setShop(shop).build()); } } diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketVehicleInteractRsp.java b/src/main/java/emu/grasscutter/server/packet/send/PacketVehicleInteractRsp.java new file mode 100644 index 000000000..73476e821 --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketVehicleInteractRsp.java @@ -0,0 +1,33 @@ +package emu.grasscutter.server.packet.send; + +import emu.grasscutter.Grasscutter; +import emu.grasscutter.game.player.Player; +import emu.grasscutter.game.entity.GameEntity; + +import emu.grasscutter.net.packet.BasePacket; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.VehicleInteractTypeOuterClass.VehicleInteractType; +import emu.grasscutter.net.proto.VehicleInteractRspOuterClass.VehicleInteractRsp; +import emu.grasscutter.net.proto.VehicleMemberOuterClass.VehicleMember; + +public class PacketVehicleInteractRsp extends BasePacket { + + public PacketVehicleInteractRsp(Player player, int entityId, VehicleInteractType interactType) { + super(PacketOpcodes.VehicleInteractRsp); + VehicleInteractRsp.Builder proto = VehicleInteractRsp.newBuilder(); + + GameEntity vehicle = player.getScene().getEntityById(entityId); + if(vehicle != null) { + proto.setEntityId(vehicle.getId()); + proto.setInteractType(interactType); + + VehicleMember vehicleMember = VehicleMember.newBuilder() + .setUid(player.getUid()) + .setAvatarGuid(player.getTeamManager().getCurrentCharacterGuid()) + .build(); + + proto.setMember(vehicleMember); + } + this.setData(proto.build()); + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketVehicleSpawnRsp.java b/src/main/java/emu/grasscutter/server/packet/send/PacketVehicleSpawnRsp.java new file mode 100644 index 000000000..69b3d8e6f --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketVehicleSpawnRsp.java @@ -0,0 +1,46 @@ +package emu.grasscutter.server.packet.send; + +import emu.grasscutter.Grasscutter; +import emu.grasscutter.game.player.Player; +import emu.grasscutter.game.entity.EntityVehicle; +import emu.grasscutter.game.props.FightProperty; +import emu.grasscutter.game.entity.GameEntity; + +import emu.grasscutter.net.packet.BasePacket; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.VehicleSpawnRspOuterClass.VehicleSpawnRsp; + +import emu.grasscutter.utils.Position; +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; + +public class PacketVehicleSpawnRsp extends BasePacket { + + public PacketVehicleSpawnRsp(Player player, int vehicleId, int pointId, Position pos, Position rot) { + super(PacketOpcodes.VehicleSpawnRsp); + VehicleSpawnRsp.Builder proto = VehicleSpawnRsp.newBuilder(); + + EntityVehicle vehicle = new EntityVehicle(player.getScene(), player, vehicleId, pointId, pos, rot); + + switch (vehicleId) { + // TODO: Not hardcode this. Waverider (skiff) + case 45001001,45001002 -> { + vehicle.addFightProperty(FightProperty.FIGHT_PROP_BASE_HP, 10000); + vehicle.addFightProperty(FightProperty.FIGHT_PROP_BASE_ATTACK, 100); + vehicle.addFightProperty(FightProperty.FIGHT_PROP_CUR_ATTACK, 100); + vehicle.addFightProperty(FightProperty.FIGHT_PROP_CUR_HP, 10000); + vehicle.addFightProperty(FightProperty.FIGHT_PROP_CUR_DEFENSE, 0); + vehicle.addFightProperty(FightProperty.FIGHT_PROP_CUR_SPEED, 0); + vehicle.addFightProperty(FightProperty.FIGHT_PROP_CHARGE_EFFICIENCY, 0); + vehicle.addFightProperty(FightProperty.FIGHT_PROP_MAX_HP, 10000); + } + default -> {} + } + + player.getScene().addEntity(vehicle); + + proto.setVehicleId(vehicleId); + proto.setEntityId(vehicle.getId()); + + this.setData(proto.build()); + } +} diff --git a/src/main/java/emu/grasscutter/utils/Utils.java b/src/main/java/emu/grasscutter/utils/Utils.java index b6c965c6b..e76556f1e 100644 --- a/src/main/java/emu/grasscutter/utils/Utils.java +++ b/src/main/java/emu/grasscutter/utils/Utils.java @@ -3,6 +3,8 @@ package emu.grasscutter.utils; import java.io.*; import java.nio.file.Files; import java.nio.file.StandardCopyOption; +import java.time.*; +import java.time.temporal.TemporalAdjusters; import java.util.Random; import emu.grasscutter.Config; @@ -192,4 +194,40 @@ public final class Utils { if(exit) System.exit(1); } + + public static int GetNextTimestampOfThisHour(int hour, String timeZone, int param) { + ZonedDateTime zonedDateTime = ZonedDateTime.now(ZoneId.of(timeZone)); + for (int i = 0; i < param; i ++){ + if (zonedDateTime.getHour() < hour) { + zonedDateTime = zonedDateTime.withHour(hour).withMinute(0).withSecond(0); + } else { + zonedDateTime = zonedDateTime.plusDays(1).withHour(hour).withMinute(0).withSecond(0); + } + } + return (int)zonedDateTime.toInstant().atZone(ZoneOffset.UTC).toEpochSecond(); + } + + public static int GetNextTimestampOfThisHourInNextWeek(int hour, String timeZone, int param) { + ZonedDateTime zonedDateTime = ZonedDateTime.now(ZoneId.of(timeZone)); + for (int i = 0; i < param; i++) { + if (zonedDateTime.getDayOfWeek() == DayOfWeek.MONDAY && zonedDateTime.getHour() < hour) { + zonedDateTime = ZonedDateTime.now(ZoneId.of(timeZone)).withHour(hour).withMinute(0).withSecond(0); + } else { + zonedDateTime = zonedDateTime.with(TemporalAdjusters.next(DayOfWeek.MONDAY)).withHour(hour).withMinute(0).withSecond(0); + } + } + return (int)zonedDateTime.toInstant().atZone(ZoneOffset.UTC).toEpochSecond(); + } + + public static int GetNextTimestampOfThisHourInNextMonth(int hour, String timeZone, int param) { + ZonedDateTime zonedDateTime = ZonedDateTime.now(ZoneId.of(timeZone)); + for (int i = 0; i < param; i++) { + if (zonedDateTime.getDayOfMonth() == 1 && zonedDateTime.getHour() < hour) { + zonedDateTime = ZonedDateTime.now(ZoneId.of(timeZone)).withHour(hour).withMinute(0).withSecond(0); + } else { + zonedDateTime = zonedDateTime.with(TemporalAdjusters.firstDayOfNextMonth()).withHour(hour).withMinute(0).withSecond(0); + } + } + return (int)zonedDateTime.toInstant().atZone(ZoneOffset.UTC).toEpochSecond(); + } }