implement npc shop

This commit is contained in:
Kengxxiao 2022-04-27 18:51:48 +08:00 committed by Melledy
parent 0e3a80407e
commit 3e0ccbbbde
11 changed files with 492 additions and 13 deletions

86
data/Shop.json Normal file
View File

@ -0,0 +1,86 @@
[
{
"shopId": 1004,
"goodsId": 1004202,
"goodsItem": {
"Id": 202,
"Count": 1000000
},
"scoin": 1,
"buyLimit": 500,
"beginTime": 1575129600,
"endTime": 2051193600,
"minLevel": 1,
"maxLevel": 99
},
{
"shopId": 1004,
"goodsId": 10048006,
"goodsItem": {
"Id": 108006,
"Count": 20
},
"scoin": 1,
"buyLimit": 50000,
"beginTime": 1575129600,
"endTime": 2051193600,
"minLevel": 1,
"maxLevel": 99
},
{
"shopId": 1004,
"goodsId": 10048033,
"goodsItem": {
"Id": 108033,
"Count": 20
},
"scoin": 1,
"buyLimit": 50000,
"beginTime": 1575129600,
"endTime": 2051193600,
"minLevel": 1,
"maxLevel": 99
},
{
"shopId": 1004,
"goodsId": 10040008,
"goodsItem": {
"Id": 220008,
"Count": 1
},
"scoin": 1,
"buyLimit": 1,
"beginTime": 1575129600,
"endTime": 2051193600,
"minLevel": 1,
"maxLevel": 99
},
{
"shopId": 1004,
"goodsId": 10044003,
"goodsItem": {
"Id": 104003,
"Count": 200
},
"scoin": 1,
"buyLimit": 50000,
"beginTime": 1575129600,
"endTime": 2051193600,
"minLevel": 1,
"maxLevel": 99
},
{
"shopId": 1004,
"goodsId": 10044013,
"goodsItem": {
"Id": 104013,
"Count": 200
},
"scoin": 1,
"buyLimit": 50000,
"beginTime": 1575129600,
"endTime": 2051193600,
"minLevel": 1,
"maxLevel": 99
}
]

View File

@ -16,6 +16,7 @@ public final class ReloadCommand implements CommandHandler {
CommandHandler.sendMessage(sender, "Reloading config."); CommandHandler.sendMessage(sender, "Reloading config.");
Grasscutter.loadConfig(); Grasscutter.loadConfig();
Grasscutter.getGameServer().getGachaManager().load(); Grasscutter.getGameServer().getGachaManager().load();
Grasscutter.getGameServer().getShopManager().load();
Grasscutter.getDispatchServer().loadQueries(); Grasscutter.getDispatchServer().loadQueries();
CommandHandler.sendMessage(sender, "Reload complete."); CommandHandler.sendMessage(sender, "Reload complete.");
} }

View File

@ -109,6 +109,16 @@ public class Inventory implements Iterable<GameItem> {
return result; return result;
} }
public boolean addItem(GameItem item, ActionReason reason, boolean forceNotify) {
boolean result = addItem(item);
if (reason != null && (forceNotify || result)) {
getPlayer().sendPacket(new PacketItemAddHintNotify(item, reason));
}
return result;
}
public void addItems(Collection<GameItem> items) { public void addItems(Collection<GameItem> items) {
this.addItems(items, null); this.addItems(items, null);

View File

@ -25,6 +25,7 @@ import emu.grasscutter.game.inventory.Inventory;
import emu.grasscutter.game.mail.Mail; import emu.grasscutter.game.mail.Mail;
import emu.grasscutter.game.props.ActionReason; import emu.grasscutter.game.props.ActionReason;
import emu.grasscutter.game.props.PlayerProperty; import emu.grasscutter.game.props.PlayerProperty;
import emu.grasscutter.game.shop.ShopLimit;
import emu.grasscutter.game.world.Scene; import emu.grasscutter.game.world.Scene;
import emu.grasscutter.game.world.World; import emu.grasscutter.game.world.World;
import emu.grasscutter.net.packet.BasePacket; import emu.grasscutter.net.packet.BasePacket;
@ -84,6 +85,7 @@ public class Player {
private ArrayList<AvatarProfileData> shownAvatars; private ArrayList<AvatarProfileData> shownAvatars;
private Set<Integer> rewardedLevels; private Set<Integer> rewardedLevels;
private ArrayList<Mail> mail; private ArrayList<Mail> mail;
private ArrayList<ShopLimit> shopLimit;
private int sceneId; private int sceneId;
private int regionId; private int regionId;
@ -141,6 +143,8 @@ public class Player {
this.birthday = new PlayerBirthday(); this.birthday = new PlayerBirthday();
this.rewardedLevels = new HashSet<>(); this.rewardedLevels = new HashSet<>();
this.moonCardGetTimes = new HashSet<>(); this.moonCardGetTimes = new HashSet<>();
this.shopLimit = new ArrayList<>();
} }
// On player creation // On player creation
@ -592,6 +596,35 @@ public class Player {
session.send(new PacketCardProductRewardNotify(getMoonCardRemainDays())); session.send(new PacketCardProductRewardNotify(getMoonCardRemainDays()));
} }
public List<ShopLimit> getShopLimit() {
return shopLimit;
}
public int getGoodsLimitNum(int goodsId) {
for (ShopLimit sl : getShopLimit()) {
if (sl.getShopGoodId() == goodsId)
return sl.getHasBought();
}
return 0;
}
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) {
ShopLimit sl = new ShopLimit();
sl.setShopGoodId(goodsId);
sl.setHasBought(boughtCount);
shopLimit.add(sl);
}
this.save();
}
public boolean inGodmode() { public boolean inGodmode() {
return godmode; return godmode;
} }

View File

@ -1,5 +1,162 @@
package emu.grasscutter.game.shop; package emu.grasscutter.game.shop;
public class ShopInfo { import emu.grasscutter.data.common.ItemParamData;
import java.util.ArrayList;
import java.util.List;
public class ShopInfo {
public int shopId = 1004;
public int goodsId = 0;
public ItemParamData goodsItem;
public int scoin = 0;
public List<ItemParamData> costItemList;
public int boughtNum = 0;
public int buyLimit = 0;
public int beginTime = 0;
public int endTime = 1924992000;
public int nextRefreshTime = 1924992000;
public int minLevel = 0;
public int maxLevel = 61;
public List<Integer> preGoodsIdList = new ArrayList<>();
public int mcoin = 0;
public int hcoin = 0;
public int disableType = 0;
public int secondarySheetId = 0;
public int getHcoin() {
return hcoin;
}
public void setHcoin(int hcoin) {
this.hcoin = hcoin;
}
public List<Integer> getPreGoodsIdList() {
return preGoodsIdList;
}
public void setPreGoodsIdList(List<Integer> preGoodsIdList) {
this.preGoodsIdList = preGoodsIdList;
}
public int getMcoin() {
return mcoin;
}
public void setMcoin(int mcoin) {
this.mcoin = mcoin;
}
public int getDisableType() {
return disableType;
}
public void setDisableType(int disableType) {
this.disableType = disableType;
}
public int getSecondarySheetId() {
return secondarySheetId;
}
public void setSecondarySheetId(int secondarySheetId) {
this.secondarySheetId = secondarySheetId;
}
public int getShopId() {
return shopId;
}
public void setShopId(int shopId) {
this.shopId = shopId;
}
public int getGoodsId() {
return goodsId;
}
public void setGoodsId(int goodsId) {
this.goodsId = goodsId;
}
public ItemParamData getGoodsItem() {
return goodsItem;
}
public void setGoodsItem(ItemParamData goodsItem) {
this.goodsItem = goodsItem;
}
public int getScoin() {
return scoin;
}
public void setScoin(int scoin) {
this.scoin = scoin;
}
public List<ItemParamData> getCostItemList() {
return costItemList;
}
public void setCostItemList(List<ItemParamData> costItemList) {
this.costItemList = costItemList;
}
public int getBoughtNum() {
return boughtNum;
}
public void setBoughtNum(int boughtNum) {
this.boughtNum = boughtNum;
}
public int getBuyLimit() {
return buyLimit;
}
public void setBuyLimit(int buyLimit) {
this.buyLimit = buyLimit;
}
public int getBeginTime() {
return beginTime;
}
public void setBeginTime(int beginTime) {
this.beginTime = beginTime;
}
public int getEndTime() {
return endTime;
}
public void setEndTime(int endTime) {
this.endTime = endTime;
}
public int getNextRefreshTime() {
return nextRefreshTime;
}
public void setNextRefreshTime(int nextRefreshTime) {
this.nextRefreshTime = nextRefreshTime;
}
public int getMinLevel() {
return minLevel;
}
public void setMinLevel(int minLevel) {
this.minLevel = minLevel;
}
public int getMaxLevel() {
return maxLevel;
}
public void setMaxLevel(int maxLevel) {
this.maxLevel = maxLevel;
}
} }

View File

@ -0,0 +1,25 @@
package emu.grasscutter.game.shop;
import dev.morphia.annotations.Entity;
@Entity
public class ShopLimit {
public int getShopGoodId() {
return shopGoodId;
}
public void setShopGoodId(int shopGoodId) {
this.shopGoodId = shopGoodId;
}
public int getHasBought() {
return hasBought;
}
public void setHasBought(int hasBought) {
this.hasBought = hasBought;
}
private int shopGoodId;
private int hasBought;
}

View File

@ -1,12 +1,50 @@
package emu.grasscutter.game.shop; package emu.grasscutter.game.shop;
import com.google.gson.reflect.TypeToken;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.server.game.GameServer; import emu.grasscutter.server.game.GameServer;
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.List;
public class ShopManager { public class ShopManager {
private final GameServer server; private final GameServer server;
public Int2ObjectMap<List<ShopInfo>> getShopData() {
return shopData;
}
private final Int2ObjectMap<List<ShopInfo>> shopData;
public ShopManager(GameServer server) { public ShopManager(GameServer server) {
this.server = server; this.server = server;
this.shopData = new Int2ObjectOpenHashMap<>();
this.load();
}
public synchronized void load() {
try (FileReader fileReader = new FileReader(Grasscutter.getConfig().DATA_FOLDER + "Shop.json")) {
getShopData().clear();
List<ShopInfo> banners = Grasscutter.getGsonFactory().fromJson(fileReader, TypeToken.getParameterized(Collection.class, ShopInfo.class).getType());
if(banners.size() > 0) {
for (ShopInfo shopInfo : banners) {
if (!getShopData().containsKey(shopInfo.getShopId()))
getShopData().put(shopInfo.getShopId(), new ArrayList<>());
getShopData().get(shopInfo.getShopId()).add(shopInfo);
Grasscutter.getLogger().info(String.format("Shop add: id [%d], data [%d*%d]", shopInfo.getShopId(), shopInfo.getGoodsItem().getId(), shopInfo.getGoodsItem().getCount()));
}
Grasscutter.getLogger().info("Shop data successfully loaded.");
} else {
Grasscutter.getLogger().error("Unable to load shop data. Shop data size is 0.");
}
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
} }
public GameServer getServer() { public GameServer getServer() {

View File

@ -0,0 +1,67 @@
package emu.grasscutter.server.packet.recv;
import emu.grasscutter.data.GameData;
import emu.grasscutter.game.inventory.GameItem;
import emu.grasscutter.game.props.ActionReason;
import emu.grasscutter.game.props.PlayerProperty;
import emu.grasscutter.net.packet.Opcodes;
import emu.grasscutter.net.packet.PacketHandler;
import emu.grasscutter.net.packet.PacketOpcodes;
import emu.grasscutter.net.proto.BuyGoodsReqOuterClass;
import emu.grasscutter.net.proto.ItemParamOuterClass;
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 java.util.ArrayList;
import java.util.HashMap;
import java.util.Optional;
@Opcodes(PacketOpcodes.BuyGoodsReq)
public class HandlerBuyGoodsReq extends PacketHandler {
@Override
public void handle(GameSession session, byte[] header, byte[] payload) throws Exception {
BuyGoodsReqOuterClass.BuyGoodsReq buyGoodsReq = BuyGoodsReqOuterClass.BuyGoodsReq.parseFrom(payload);
for (ShopGoodsOuterClass.ShopGoods sg : buyGoodsReq.getGoodsListList()) {
if (sg.getScoin() > 0 && session.getPlayer().getMora() < buyGoodsReq.getBoughtNum() * sg.getScoin()) {
return;
}
if (sg.getHcoin() > 0 && session.getPlayer().getPrimogems() < buyGoodsReq.getBoughtNum() * sg.getHcoin()) {
return;
}
if (sg.getMcoin() > 0 && session.getPlayer().getProperty(PlayerProperty.PROP_PLAYER_MCOIN) < buyGoodsReq.getBoughtNum() * sg.getMcoin()) {
return;
}
HashMap<GameItem, Integer> itemsCache = new HashMap<>();
for (ItemParamOuterClass.ItemParam p : sg.getCostItemListList()) {
Optional<GameItem> 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());
}
session.getPlayer().setMora(session.getPlayer().getMora() - buyGoodsReq.getBoughtNum() * sg.getScoin());
session.getPlayer().setPrimogems(session.getPlayer().getPrimogems() - buyGoodsReq.getBoughtNum() * sg.getHcoin());
session.getPlayer().setProperty(PlayerProperty.PROP_PLAYER_MCOIN, session.getPlayer().getProperty(PlayerProperty.PROP_PLAYER_MCOIN) - buyGoodsReq.getBoughtNum() * sg.getMcoin());
if (!itemsCache.isEmpty()) {
for (GameItem gi : itemsCache.keySet()) {
session.getPlayer().getInventory().removeItem(gi, itemsCache.get(gi));
}
itemsCache.clear();
}
session.getPlayer().addShopLimit(sg.getGoodsId(), buyGoodsReq.getBoughtNum());
GameItem item = new GameItem(GameData.getItemDataMap().get(sg.getGoodsItem().getItemId()));
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.getPlayer().save();
}
}

View File

@ -1,9 +1,9 @@
package emu.grasscutter.server.packet.recv; package emu.grasscutter.server.packet.recv;
import emu.grasscutter.net.packet.Opcodes; import emu.grasscutter.net.packet.Opcodes;
import emu.grasscutter.net.packet.PacketHandler;
import emu.grasscutter.net.packet.PacketOpcodes; import emu.grasscutter.net.packet.PacketOpcodes;
import emu.grasscutter.net.proto.GetShopReqOuterClass.GetShopReq; import emu.grasscutter.net.proto.GetShopReqOuterClass.GetShopReq;
import emu.grasscutter.net.packet.PacketHandler;
import emu.grasscutter.server.game.GameSession; import emu.grasscutter.server.game.GameSession;
import emu.grasscutter.server.packet.send.PacketGetShopRsp; import emu.grasscutter.server.packet.send.PacketGetShopRsp;
@ -12,8 +12,7 @@ public class HandlerGetShopReq extends PacketHandler {
@Override @Override
public void handle(GameSession session, byte[] header, byte[] payload) throws Exception { public void handle(GameSession session, byte[] header, byte[] payload) throws Exception {
GetShopReq req = GetShopReq.parseFrom(payload); GetShopReq req = GetShopReq.parseFrom(payload);
// TODO session.send(new PacketGetShopRsp(session.getPlayer(), req.getShopType()));
session.send(new PacketGetShopRsp(req.getShopType()));
} }
} }

View File

@ -0,0 +1,22 @@
package emu.grasscutter.server.packet.send;
import emu.grasscutter.net.packet.BasePacket;
import emu.grasscutter.net.packet.PacketOpcodes;
import emu.grasscutter.net.proto.BuyGoodsRspOuterClass;
import emu.grasscutter.net.proto.ShopGoodsOuterClass;
public class PacketBuyGoodsRsp extends BasePacket {
public PacketBuyGoodsRsp(int shopType, int boughtNum, ShopGoodsOuterClass.ShopGoods sg) {
super(PacketOpcodes.BuyGoodsRsp);
BuyGoodsRspOuterClass.BuyGoodsRsp buyGoodsRsp = BuyGoodsRspOuterClass.BuyGoodsRsp.newBuilder()
.setShopType(shopType)
.setBoughtNum(boughtNum)
.addGoodsList(ShopGoodsOuterClass.ShopGoods.newBuilder()
.mergeFrom(sg)
.setBoughtNum(boughtNum)
).build();
this.setData(buyGoodsRsp);
}
}

View File

@ -1,19 +1,60 @@
package emu.grasscutter.server.packet.send; 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.ShopManager;
import emu.grasscutter.net.packet.BasePacket; import emu.grasscutter.net.packet.BasePacket;
import emu.grasscutter.net.packet.PacketOpcodes; import emu.grasscutter.net.packet.PacketOpcodes;
import emu.grasscutter.net.proto.GetShopRspOuterClass.GetShopRsp; 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.net.proto.ShopOuterClass.Shop;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
public class PacketGetShopRsp extends BasePacket { public class PacketGetShopRsp extends BasePacket {
public PacketGetShopRsp(int shopType) { public PacketGetShopRsp(Player inv, int shopType) {
super(PacketOpcodes.GetShopRsp); super(PacketOpcodes.GetShopRsp);
GetShopRsp proto = GetShopRsp.newBuilder() // TODO: CityReputationLevel
.setShop(Shop.newBuilder().setShopType(shopType)) Shop.Builder shop = Shop.newBuilder()
.build(); .setShopType(shopType)
.setCityId(1) //mock
this.setData(proto); .setCityReputationLevel(10); //mock
ShopManager manager = Grasscutter.getGameServer().getShopManager();
if (manager.getShopData().get(shopType) != null) {
List<ShopInfo> list = manager.getShopData().get(shopType);
List<ShopGoods> goodsList = new ArrayList<>();
for (ShopInfo info : list) {
ShopGoods.Builder goods = ShopGoods.newBuilder()
.setGoodsId(info.getGoodsId())
.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()));
}
goodsList.add(goods.build());
}
shop.addAllGoodsList(goodsList);
}
this.setData(GetShopRspOuterClass.GetShopRsp.newBuilder().setShop(shop).build());
} }
} }