Commit a2ff8c84 authored by KingRainbow44's avatar KingRainbow44
Browse files

Merge `development` into `plugin-auth`

parents 3adf0d44 a751e71d
package emu.grasscutter.game.quest.handlers;
import emu.grasscutter.data.def.QuestData.QuestCondition;
import emu.grasscutter.game.quest.GameQuest;
public abstract class QuestBaseHandler {
public abstract boolean execute(GameQuest quest, QuestCondition condition, int... params);
}
......@@ -39,7 +39,8 @@ public class TowerScheduleManager {
public TowerScheduleData getCurrentTowerScheduleData(){
var data = GameData.getTowerScheduleDataMap().get(towerScheduleConfig.getScheduleId());
if(data == null){
Grasscutter.getLogger().error("Could not get current tower schedule data by config:{}", towerScheduleConfig);
Grasscutter.getLogger().error("Could not get current tower schedule data by schedule id {}, please check your resource files",
towerScheduleConfig.getScheduleId());
}
return data;
......
......@@ -10,6 +10,7 @@ import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.player.Player.SceneLoadState;
import emu.grasscutter.game.props.EnterReason;
import emu.grasscutter.game.props.EntityIdType;
import emu.grasscutter.game.props.SceneType;
import emu.grasscutter.data.GameData;
import emu.grasscutter.data.def.DungeonData;
import emu.grasscutter.data.def.SceneData;
......@@ -267,6 +268,9 @@ public class World implements Iterable<Player> {
enterReason = EnterReason.DungeonEnter;
} else if (oldScene == newScene) {
enterType = EnterType.ENTER_GOTO;
} else if (newScene.getSceneType() == SceneType.SCENE_HOME_WORLD) {
// Home
enterType = EnterType.ENTER_SELF_HOME;
}
// Teleport packet
......
......@@ -14,6 +14,8 @@ import emu.grasscutter.game.managers.ChatManager;
import emu.grasscutter.game.managers.InventoryManager;
import emu.grasscutter.game.managers.MultiplayerManager;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.quest.ServerQuestHandler;
import emu.grasscutter.game.quest.handlers.QuestBaseHandler;
import emu.grasscutter.game.shop.ShopManager;
import emu.grasscutter.game.tower.TowerScheduleManager;
import emu.grasscutter.game.world.World;
......@@ -37,7 +39,8 @@ import static emu.grasscutter.Configuration.*;
public final class GameServer extends KcpServer {
private final InetSocketAddress address;
private final GameServerPacketHandler packetHandler;
private final ServerQuestHandler questHandler;
private final Map<Integer, Player> players;
private final Set<World> worlds;
......@@ -68,6 +71,7 @@ public final class GameServer extends KcpServer {
this.setServerInitializer(new GameServerInitializer(this));
this.address = address;
this.packetHandler = new GameServerPacketHandler(PacketHandler.class);
this.questHandler = new ServerQuestHandler();
this.players = new ConcurrentHashMap<>();
this.worlds = Collections.synchronizedSet(new HashSet<>());
......@@ -91,6 +95,10 @@ public final class GameServer extends KcpServer {
return packetHandler;
}
public ServerQuestHandler getQuestHandler() {
return questHandler;
}
public Map<Integer, Player> getPlayers() {
return players;
}
......
......@@ -252,6 +252,7 @@ public class GameSession extends KcpChannel {
} catch (Exception e) {
e.printStackTrace();
} finally {
data.release();
packet.release();
}
}
......
......@@ -5,10 +5,13 @@ import com.google.protobuf.InvalidProtocolBufferException;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.Grasscutter.ServerRunMode;
import emu.grasscutter.net.proto.QueryCurrRegionHttpRspOuterClass.*;
import emu.grasscutter.net.proto.RegionInfoOuterClass;
import emu.grasscutter.net.proto.RegionInfoOuterClass.RegionInfo;
import emu.grasscutter.net.proto.RegionSimpleInfoOuterClass.RegionSimpleInfo;
import emu.grasscutter.server.event.dispatch.QueryAllRegionsEvent;
import emu.grasscutter.server.event.dispatch.QueryCurrentRegionEvent;
import emu.grasscutter.server.http.Router;
import emu.grasscutter.utils.Crypto;
import emu.grasscutter.utils.FileUtils;
import emu.grasscutter.utils.Utils;
import express.Express;
......@@ -30,45 +33,24 @@ import static emu.grasscutter.net.proto.QueryRegionListHttpRspOuterClass.*;
* Handles requests related to region queries.
*/
public final class RegionHandler implements Router {
private String regionQuery = "";
private String regionList = "";
private static final Map<String, RegionData> regions = new ConcurrentHashMap<>();
private static String regionListResponse;
public RegionHandler() {
try { // Read & initialize region data.
this.readRegionData();
this.initialize();
} catch (Exception exception) {
Grasscutter.getLogger().error("Failed to initialize region data.", exception);
}
}
/**
* Loads initial region data.
*/
private void readRegionData() {
File file;
file = new File(DATA("query_region_list.txt"));
if (file.exists())
this.regionList = new String(FileUtils.read(file));
else Grasscutter.getLogger().error("[Dispatch] 'query_region_list' not found!");
file = new File(DATA("query_cur_region.txt"));
if (file.exists())
regionQuery = new String(FileUtils.read(file));
else Grasscutter.getLogger().warn("[Dispatch] 'query_cur_region' not found!");
}
/**
* Configures region data according to configuration.
*/
private void initialize() throws InvalidProtocolBufferException {
// Decode the initial region query.
byte[] queryBase64 = Base64.getDecoder().decode(this.regionQuery);
QueryCurrRegionHttpRsp regionQuery = QueryCurrRegionHttpRsp.parseFrom(queryBase64);
private void initialize() {
String dispatchDomain = "http" + (HTTP_ENCRYPTION.useInRouting ? "s" : "") + "://"
+ lr(HTTP_INFO.accessAddress, HTTP_INFO.bindAddress) + ":"
+ lr(HTTP_INFO.accessPort, HTTP_INFO.bindPort);
// Create regions.
List<RegionSimpleInfo> servers = new ArrayList<>();
......@@ -87,37 +69,33 @@ public final class RegionHandler implements Router {
Grasscutter.getLogger().error("Region name already in use.");
return;
}
// Create a region identifier.
var identifier = RegionSimpleInfo.newBuilder()
.setName(region.Name).setTitle(region.Title)
.setType("DEV_PUBLIC").setDispatchUrl(
"http" + (HTTP_ENCRYPTION.useInRouting ? "s" : "") + "://"
+ lr(HTTP_INFO.accessAddress, HTTP_INFO.bindAddress) + ":"
+ lr(HTTP_INFO.accessPort, HTTP_INFO.bindPort)
+ "/query_cur_region/" + region.Name)
.setName(region.Name).setTitle(region.Title).setType("DEV_PUBLIC")
.setDispatchUrl(dispatchDomain + "/query_cur_region/" + region.Name)
.build();
usedNames.add(region.Name); servers.add(identifier);
// Create a region info object.
var regionInfo = regionQuery.getRegionInfo().toBuilder()
var regionInfo = RegionInfo.newBuilder()
.setGateserverIp(region.Ip).setGateserverPort(region.Port)
.setSecretKey(ByteString.copyFrom(FileUtils.read(KEYS_FOLDER + "/dispatchSeed.bin")))
.setSecretKey(ByteString.copyFrom(Crypto.DISPATCH_SEED))
.build();
// Create an updated region query.
var updatedQuery = regionQuery.toBuilder().setRegionInfo(regionInfo).build();
var updatedQuery = QueryCurrRegionHttpRsp.newBuilder().setRegionInfo(regionInfo).build();
regions.put(region.Name, new RegionData(updatedQuery, Utils.base64Encode(updatedQuery.toByteString().toByteArray())));
});
// Decode the initial region list.
byte[] listBase64 = Base64.getDecoder().decode(this.regionList);
QueryRegionListHttpRsp regionList = QueryRegionListHttpRsp.parseFrom(listBase64);
// Create a config object.
byte[] customConfig = "{\"sdkenv\":\"2\",\"checkdevice\":\"false\",\"loadPatch\":\"false\",\"showexception\":\"false\",\"regionConfig\":\"pm|fk|add\",\"downloadMode\":\"0\"}".getBytes();
Crypto.xor(customConfig, Crypto.DISPATCH_KEY); // XOR the config with the key.
// Create an updated region list.
QueryRegionListHttpRsp updatedRegionList = QueryRegionListHttpRsp.newBuilder()
.addAllRegionList(servers)
.setClientSecretKey(regionList.getClientSecretKey())
.setClientCustomConfigEncrypted(regionList.getClientCustomConfigEncrypted())
.setClientSecretKey(ByteString.copyFrom(Crypto.DISPATCH_SEED))
.setClientCustomConfigEncrypted(ByteString.copyFrom(customConfig))
.setEnableLoginPc(true).build();
// Set the region list response.
......
......@@ -3,6 +3,8 @@ package emu.grasscutter.server.http.handlers;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.server.http.objects.HttpJsonResponse;
import emu.grasscutter.server.http.Router;
import emu.grasscutter.utils.FileUtils;
import emu.grasscutter.utils.Utils;
import express.Express;
import express.http.Request;
import express.http.Response;
......@@ -11,6 +13,7 @@ import io.javalin.Javalin;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.nio.file.Paths;
import java.util.Objects;
import static emu.grasscutter.Configuration.DATA;
......@@ -19,6 +22,18 @@ import static emu.grasscutter.Configuration.DATA;
* Handles requests related to the announcements page.
*/
public final class AnnouncementsHandler implements Router {
private static String template, swjs, vue;
public AnnouncementsHandler() {
var templateFile = new File(Utils.toFilePath(DATA("/hk4e/announcement/index.html")));
var swjsFile = new File(Utils.toFilePath(DATA("/hk4e/announcement/sw.js")));
var vueFile = new File(Utils.toFilePath(DATA("/hk4e/announcement/vue.min.js")));
template = templateFile.exists() ? new String(FileUtils.read(template)) : null;
swjs = swjsFile.exists() ? new String(FileUtils.read(swjs)) : null;
vue = vueFile.exists() ? new String(FileUtils.read(vueFile)) : null;
}
@Override public void applyRoutes(Express express, Javalin handle) {
// hk4e-api-os.hoyoverse.com
express.all("/common/hk4e_global/announcement/api/getAlertPic", new HttpJsonResponse("{\"retcode\":0,\"message\":\"OK\",\"data\":{\"total\":0,\"list\":[]}}"));
......@@ -30,14 +45,45 @@ public final class AnnouncementsHandler implements Router {
express.all("/common/hk4e_global/announcement/api/getAnnContent", AnnouncementsHandler::getAnnouncement);
// hk4e-sdk-os.hoyoverse.com
express.all("/hk4e_global/mdk/shopwindow/shopwindow/listPriceTier", new HttpJsonResponse("{\"retcode\":0,\"message\":\"OK\",\"data\":{\"suggest_currency\":\"USD\",\"tiers\":[]}}"));
express.get("/hk4e/announcement/*", AnnouncementsHandler::getPageResources);
express.get("/sw.js", AnnouncementsHandler::getPageResources);
express.get("/dora/lib/vue/2.6.11/vue.min.js", AnnouncementsHandler::getPageResources);
}
private static void getAnnouncement(Request request, Response response) {
if (Objects.equals(request.baseUrl(), "/common/hk4e_global/announcement/api/getAnnContent")) {
response.send("{\"retcode\":0,\"message\":\"OK\",\"data\":" + readToString(new File(DATA("GameAnnouncement.json"))) +"}");
String data = readToString(Paths.get(DATA("GameAnnouncement.json")).toFile());
response.send("{\"retcode\":0,\"message\":\"OK\",\"data\":" + data + "}");
} else if (Objects.equals(request.baseUrl(), "/common/hk4e_global/announcement/api/getAnnList")) {
String data = readToString(new File(DATA("GameAnnouncementList.json"))).replace("System.currentTimeMillis()",String.valueOf(System.currentTimeMillis()));
response.send("{\"retcode\":0,\"message\":\"OK\",\"data\": "+data +"}");
String data = readToString(Paths.get(DATA("GameAnnouncementList.json")).toFile())
.replace("System.currentTimeMillis()", String.valueOf(System.currentTimeMillis()));
response.send("{\"retcode\":0,\"message\":\"OK\",\"data\": " + data + "}");
}
}
private static void getPageResources(Request request, Response response) {
var path = request.path();
switch(path) {
case "/sw.js" -> response.send(swjs);
case "/hk4e/announcement/index.html" -> response.send(template);
case "/dora/lib/vue/2.6.11/vue.min.js" -> response.send(vue);
default -> {
File renderFile = new File(Utils.toFilePath(DATA(path)));
if(!renderFile.exists()) {
Grasscutter.getLogger().info("File not exist: " + path);
return;
}
String ext = path.substring(path.lastIndexOf(".") + 1);
if ("css".equals(ext)) {
response.type("text/css");
response.send(FileUtils.read(renderFile));
} else {
response.send(FileUtils.read(renderFile));
}
}
}
}
......
......@@ -2,6 +2,10 @@ package emu.grasscutter.server.http.handlers;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.database.DatabaseHelper;
import emu.grasscutter.game.Account;
import emu.grasscutter.game.gacha.GachaBanner;
import emu.grasscutter.game.gacha.GachaManager;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.server.http.Router;
import emu.grasscutter.tools.Tools;
import emu.grasscutter.utils.FileUtils;
......@@ -13,8 +17,12 @@ import io.javalin.Javalin;
import io.javalin.http.staticfiles.Location;
import java.io.File;
import java.util.Arrays;
import java.util.LinkedHashSet;
import java.util.Set;
import static emu.grasscutter.Configuration.DATA;
import static emu.grasscutter.utils.Language.translate;
/**
* Handles all gacha-related HTTP requests.
......@@ -22,7 +30,8 @@ import static emu.grasscutter.Configuration.DATA;
public final class GachaHandler implements Router {
private final String gachaMappings;
private static String frontendTemplate = "{{REPLACE_RECORD}}";
private static String recordsTemplate = "";
private static String detailsTemplate = "";
public GachaHandler() {
this.gachaMappings = Utils.toFilePath(DATA("/gacha_mappings.js"));
......@@ -35,12 +44,15 @@ public final class GachaHandler implements Router {
}
var templateFile = new File(DATA("/gacha_records.html"));
if(templateFile.exists())
frontendTemplate = new String(FileUtils.read(templateFile));
recordsTemplate = templateFile.exists() ? new String(FileUtils.read(templateFile)) : "{{REPLACE_RECORD}}";
templateFile = new File(Utils.toFilePath(DATA("/gacha_details.html")));
detailsTemplate = templateFile.exists() ? new String(FileUtils.read(templateFile)) : null;
}
@Override public void applyRoutes(Express express, Javalin handle) {
express.get("/gacha", GachaHandler::gachaRecords);
express.get("/gacha/details", GachaHandler::gachaDetails);
express.useStaticFallback("/gacha/mappings", this.gachaMappings, Location.EXTERNAL);
}
......@@ -63,9 +75,62 @@ public final class GachaHandler implements Router {
String records = DatabaseHelper.getGachaRecords(account.getPlayerUid(), gachaType, page).toString();
long maxPage = DatabaseHelper.getGachaRecordsMaxPage(account.getPlayerUid(), page, gachaType);
response.send(frontendTemplate
response.send(recordsTemplate
.replace("{{REPLACE_RECORD}}", records)
.replace("{{REPLACE_MAXPAGE}}", String.valueOf(maxPage)));
}
}
private static void gachaDetails(Request request, Response response) {
String template = detailsTemplate;
// Get player info (for langauge).
String sessionKey = request.query("s");
Account account = DatabaseHelper.getAccountBySessionKey(sessionKey);
Player player = Grasscutter.getGameServer().getPlayerByUid(account.getPlayerUid());
// If the template was not loaded, return an error.
if (detailsTemplate == null) {
response.send(translate(player, "gacha.details.template_missing"));
return;
}
// Add translated title etc. to the page.
template = template.replace("{{TITLE}}", translate(player, "gacha.details.title"))
.replace("{{AVAILABLE_FIVE_STARS}}", translate(player, "gacha.details.available_five_stars"))
.replace("{{AVAILABLE_FOUR_STARS}}", translate(player, "gacha.details.available_four_stars"))
.replace("{{AVAILABLE_THREE_STARS}}", translate(player, "gacha.details.available_three_stars"))
.replace("{{LANGUAGE}}", Utils.getLanguageCode(account.getLocale()));
// Get the banner info for the banner we want.
int gachaType = Integer.parseInt(request.query("gachaType"));
GachaManager manager = Grasscutter.getGameServer().getGachaManager();
GachaBanner banner = manager.getGachaBanners().get(gachaType);
// Add 5-star items.
Set<String> fiveStarItems = new LinkedHashSet<>();
Arrays.stream(banner.getRateUpItems5()).forEach(i -> fiveStarItems.add(Integer.toString(i)));
Arrays.stream(banner.getFallbackItems5Pool1()).forEach(i -> fiveStarItems.add(Integer.toString(i)));
Arrays.stream(banner.getFallbackItems5Pool2()).forEach(i -> fiveStarItems.add(Integer.toString(i)));
template = template.replace("{{FIVE_STARS}}", "[" + String.join(",", fiveStarItems) + "]");
// Add 4-star items.
Set<String> fourStarItems = new LinkedHashSet<>();
Arrays.stream(banner.getRateUpItems4()).forEach(i -> fourStarItems.add(Integer.toString(i)));
Arrays.stream(banner.getFallbackItems4Pool1()).forEach(i -> fourStarItems.add(Integer.toString(i)));
Arrays.stream(banner.getFallbackItems4Pool2()).forEach(i -> fourStarItems.add(Integer.toString(i)));
template = template.replace("{{FOUR_STARS}}", "[" + String.join(",", fourStarItems) + "]");
// Add 3-star items.
Set<String> threeStarItems = new LinkedHashSet<>();
Arrays.stream(banner.getFallbackItems3()).forEach(i -> threeStarItems.add(Integer.toString(i)));
template = template.replace("{{THREE_STARS}}", "[" + String.join(",", threeStarItems) + "]");
// Done.
response.send(template);
}
}
......@@ -18,7 +18,7 @@ import emu.grasscutter.server.packet.send.PacketBuyGoodsRsp;
import emu.grasscutter.server.packet.send.PacketStoreItemChangeNotify;
import emu.grasscutter.utils.Utils;
import java.util.HashMap;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
......@@ -56,36 +56,13 @@ public class HandlerBuyGoodsReq extends PacketHandler {
return;
}
if (sg.getScoin() > 0 && session.getPlayer().getMora() < buyGoodsReq.getBoughtNum() * sg.getScoin()) {
List<ItemParamData> costs = new ArrayList<ItemParamData>(sg.getCostItemList()); // Can this even be null?
costs.add(new ItemParamData(202, sg.getScoin()));
costs.add(new ItemParamData(201, sg.getHcoin()));
costs.add(new ItemParamData(203, sg.getMcoin()));
if (!session.getPlayer().getInventory().payItems(costs.toArray(new ItemParamData[0]), buyGoodsReq.getBoughtNum())) {
return;
}
if (sg.getHcoin() > 0 && session.getPlayer().getPrimogems() < buyGoodsReq.getBoughtNum() * sg.getHcoin()) {
return;
}
if (sg.getMcoin() > 0 && session.getPlayer().getCrystals() < buyGoodsReq.getBoughtNum() * sg.getMcoin()) {
return;
}
HashMap<GameItem, Integer> itemsCache = new HashMap<>();
if (sg.getCostItemList() != null) {
for (ItemParamData p : sg.getCostItemList()) {
Optional<GameItem> 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());
session.getPlayer().setPrimogems(session.getPlayer().getPrimogems() - buyGoodsReq.getBoughtNum() * sg.getHcoin());
session.getPlayer().setCrystals(session.getPlayer().getCrystals() - 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(), ShopManager.getShopNextRefreshTime(sg));
GameItem item = new GameItem(GameData.getItemDataMap().get(sg.getGoodsItem().getId()));
......
......@@ -11,12 +11,6 @@ import emu.grasscutter.server.game.GameSession;
public class HandlerEnterTransPointRegionNotify extends PacketHandler {
@Override
public void handle(GameSession session, byte[] header, byte[] payload) throws Exception{
Player player = session.getPlayer();
SotSManager sotsManager = player.getSotSManager();
sotsManager.refillSpringVolume();
sotsManager.autoRevive(session);
sotsManager.scheduleAutoRecover(session);
// TODO: allow interaction with the SotS?
session.getPlayer().getSotSManager().handleEnterTransPointRegionNotify();
}
}
package emu.grasscutter.server.packet.recv;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.game.managers.SotSManager;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.net.packet.Opcodes;
......@@ -11,8 +12,6 @@ import emu.grasscutter.server.game.GameSession;
public class HandlerExitTransPointRegionNotify extends PacketHandler {
@Override
public void handle(GameSession session, byte[] header, byte[] payload) throws Exception{
Player player = session.getPlayer();
SotSManager sotsManager = player.getSotSManager();
sotsManager.cancelAutoRecover();
session.getPlayer().getSotSManager().handleExitTransPointRegionNotify();
}
}
package emu.grasscutter.server.packet.recv;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.net.packet.Opcodes;
import emu.grasscutter.net.packet.PacketOpcodes;
import emu.grasscutter.net.packet.PacketHandler;
import emu.grasscutter.server.game.GameSession;
import emu.grasscutter.server.packet.send.PacketGetShopRsp;
import emu.grasscutter.server.packet.send.PacketGetWidgetSlotRsp;
@Opcodes(PacketOpcodes.GetWidgetSlotReq)
public class HandlerGetWidgetSlotReq extends PacketHandler {
@Override
public void handle(GameSession session, byte[] header, byte[] payload) throws Exception {
// Unhandled
Player player = session.getPlayer();
session.send(new PacketGetWidgetSlotRsp(player));
}
}
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.HomeChooseModuleReqOuterClass;
import emu.grasscutter.server.game.GameSession;
import emu.grasscutter.server.packet.send.PacketHomeChooseModuleRsp;
import emu.grasscutter.server.packet.send.PacketHomeComfortInfoNotify;
import emu.grasscutter.server.packet.send.PacketPlayerHomeCompInfoNotify;
@Opcodes(PacketOpcodes.HomeChooseModuleReq)
public class HandlerHomeChooseModuleReq extends PacketHandler {
@Override
public void handle(GameSession session, byte[] header, byte[] payload) throws Exception {
HomeChooseModuleReqOuterClass.HomeChooseModuleReq req =
HomeChooseModuleReqOuterClass.HomeChooseModuleReq.parseFrom(payload);
session.getPlayer().addRealmList(req.getModuleId());
session.getPlayer().setCurrentRealmId(req.getModuleId());
session.send(new PacketHomeChooseModuleRsp(req.getModuleId()));
session.send(new PacketPlayerHomeCompInfoNotify(session.getPlayer()));
session.send(new PacketHomeComfortInfoNotify(session.getPlayer()));
}
}
package emu.grasscutter.server.packet.recv;
import emu.grasscutter.game.managers.MapMarkManager.MapMark;
import emu.grasscutter.game.managers.MapMarkManager.MapMarksManager;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.net.packet.Opcodes;
import emu.grasscutter.net.packet.PacketHandler;
import emu.grasscutter.net.packet.PacketOpcodes;
import emu.grasscutter.net.proto.*;
import emu.grasscutter.net.proto.MarkMapReqOuterClass.MarkMapReq;
import emu.grasscutter.server.game.GameSession;
import emu.grasscutter.server.packet.send.PacketMarkMapRsp;
import emu.grasscutter.server.packet.send.PacketMarkNewNotify;
import emu.grasscutter.server.packet.send.PacketSceneEntityAppearNotify;
import emu.grasscutter.utils.Position;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
@Opcodes(PacketOpcodes.MarkMapReq)
public class HandlerMarkMapReq extends PacketHandler {
private static boolean isInt(String str) {
try {
@SuppressWarnings("unused")
int x = Integer.parseInt(str);
return true; // String is an Integer
} catch (NumberFormatException e) {
return false; // String is not an Integer
}
}
@Override
public void handle(GameSession session, byte[] header, byte[] payload) throws Exception {
MarkMapReq req = MarkMapReq.parseFrom(payload);
MarkMapReq.Operation op = req.getOp();
Player player = session.getPlayer();
MapMarksManager mapMarksManager = player.getMapMarksManager();
if (op == MarkMapReq.Operation.ADD) {
MapMark newMapMark = new MapMark(req.getMark());
// keep teleporting functionality on fishhook mark.
if (newMapMark.getMapMarkPointType() == MapMarkPointTypeOuterClass.MapMarkPointType.MAP_MARK_POINT_TYPE_FISH_POOL) {
teleport(player, newMapMark);
return;
}
if (mapMarksManager.addMapMark(newMapMark)) {
player.save();
}
} else if (op == MarkMapReq.Operation.MOD) {
MapMark newMapMark = new MapMark(req.getMark());
if (mapMarksManager.removeMapMark(newMapMark.getPosition())) {
if (mapMarksManager.addMapMark(newMapMark)) {
player.save();
}
}
} else if (op == MarkMapReq.Operation.DEL) {
MapMark newMapMark = new MapMark(req.getMark());
if (mapMarksManager.removeMapMark(newMapMark.getPosition())) {
player.save();
}
} else if (op == MarkMapReq.Operation.GET) {
// no-op
}
// send all marks to refresh client map view.
HashMap<String, MapMark> mapMarks = mapMarksManager.getAllMapMarks();
session.send(new PacketMarkMapRsp(player, mapMarks));
}
private void teleport(Player player, MapMark mapMark) {
float y = isInt(mapMark.getName()) ? Integer.parseInt(mapMark.getName()) : 300;
float x = mapMark.getPosition().getX();
float z = mapMark.getPosition().getZ();
player.getPos().set(x, y, z);
if (mapMark.getSceneId() != player.getSceneId()) {
player.getWorld().transferPlayerToScene(player, mapMark.getSceneId(),
player.getPos());
} else {
player.getScene().broadcastPacket(new PacketSceneEntityAppearNotify(player));
}
session.getPlayer().getMapMarksManager().handleMapMarkReq(req);
}
}
package emu.grasscutter.server.packet.recv;
import emu.grasscutter.game.inventory.GameItem;
import emu.grasscutter.game.quest.enums.QuestTrigger;
import emu.grasscutter.net.packet.Opcodes;
import emu.grasscutter.net.packet.PacketOpcodes;
import emu.grasscutter.net.proto.NpcTalkReqOuterClass.NpcTalkReq;
......@@ -14,6 +15,10 @@ public class HandlerNpcTalkReq extends PacketHandler {
@Override
public void handle(GameSession session, byte[] header, byte[] payload) throws Exception {
NpcTalkReq req = NpcTalkReq.parseFrom(payload);
// Why are there 2 quest triggers that do the same thing...
session.getPlayer().getQuestManager().triggerEvent(QuestTrigger.QUEST_CONTENT_COMPLETE_TALK, req.getTalkId());
session.getPlayer().getQuestManager().triggerEvent(QuestTrigger.QUEST_CONTENT_FINISH_PLOT, req.getTalkId());
session.send(new PacketNpcTalkRsp(req.getNpcEntityId(), req.getTalkId(), req.getEntityId()));
}
......
package emu.grasscutter.server.packet.recv;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.net.packet.Opcodes;
import emu.grasscutter.net.packet.PacketHandler;
import emu.grasscutter.net.packet.PacketOpcodes;
import emu.grasscutter.net.proto.SetWidgetSlotReqOuterClass;
import emu.grasscutter.net.proto.WidgetSlotOpOuterClass;
import emu.grasscutter.server.game.GameSession;
import emu.grasscutter.server.packet.send.PacketSetWidgetSlotRsp;
import emu.grasscutter.server.packet.send.PacketWidgetSlotChangeNotify;
@Opcodes(PacketOpcodes.SetWidgetSlotReq)
public class HandlerSetWidgetSlotReq extends PacketHandler {
@Override
public void handle(GameSession session, byte[] header, byte[] payload) throws Exception {
SetWidgetSlotReqOuterClass.SetWidgetSlotReq req = SetWidgetSlotReqOuterClass.SetWidgetSlotReq.parseFrom(payload);
Player player = session.getPlayer();
player.setWidgetId(req.getMaterialId());
// WidgetSlotChangeNotify op & slot key
session.send(new PacketWidgetSlotChangeNotify(WidgetSlotOpOuterClass.WidgetSlotOp.DETACH));
// WidgetSlotChangeNotify slot
session.send(new PacketWidgetSlotChangeNotify(req.getMaterialId()));
// SetWidgetSlotRsp
session.send(new PacketSetWidgetSlotRsp(req.getMaterialId()));
}
}
package emu.grasscutter.server.packet.recv;
import emu.grasscutter.data.GameData;
import emu.grasscutter.game.world.Scene;
import emu.grasscutter.net.packet.Opcodes;
import emu.grasscutter.net.packet.PacketHandler;
import emu.grasscutter.net.packet.PacketOpcodes;
import emu.grasscutter.net.proto.TryEnterHomeReqOuterClass;
import emu.grasscutter.scripts.data.SceneConfig;
import emu.grasscutter.server.game.GameSession;
import emu.grasscutter.server.packet.send.PacketTryEnterHomeRsp;
import emu.grasscutter.utils.Position;
@Opcodes(PacketOpcodes.TryEnterHomeReq)
public class HandlerTryEnterHomeReq extends PacketHandler {
@Override
public void handle(GameSession session, byte[] header, byte[] payload) throws Exception {
TryEnterHomeReqOuterClass.TryEnterHomeReq req =
TryEnterHomeReqOuterClass.TryEnterHomeReq.parseFrom(payload);
if (req.getTargetUid() != session.getPlayer().getUid()) {
// I hope that tomorrow there will be a hero who can support multiplayer mode and write code like a poem
session.send(new PacketTryEnterHomeRsp());
return;
}
int realmId = 2000 + session.getPlayer().getCurrentRealmId();
Scene scene = session.getPlayer().getWorld().getSceneById(realmId);
Position pos = scene.getScriptManager().getConfig().born_pos;
session.getPlayer().getWorld().transferPlayerToScene(
session.getPlayer(),
realmId,
pos
);
session.send(new PacketTryEnterHomeRsp(req.getTargetUid()));
}
}
......@@ -14,6 +14,7 @@ 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.getPlayer().getStaminaManager().handleVehicleInteractReq(session, req.getEntityId(), req.getInteractType());
session.send(new PacketVehicleInteractRsp(session.getPlayer(), req.getEntityId(), req.getInteractType()));
}
}
package emu.grasscutter.server.packet.recv;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.data.GameData;
import emu.grasscutter.data.def.GadgetData;
import emu.grasscutter.game.entity.EntityVehicle;
import emu.grasscutter.game.entity.GameEntity;
import emu.grasscutter.game.props.LifeState;
import emu.grasscutter.net.packet.Opcodes;
import emu.grasscutter.net.packet.PacketHandler;
import emu.grasscutter.net.packet.PacketOpcodes;
import emu.grasscutter.net.proto.*;
import emu.grasscutter.server.game.GameSession;
import emu.grasscutter.server.packet.send.PacketSceneEntityAppearNotify;
import emu.grasscutter.server.packet.send.PacketWidgetCoolDownNotify;
import emu.grasscutter.server.packet.send.PacketWidgetDoBagRsp;
import emu.grasscutter.server.packet.send.PacketWidgetGadgetDataNotify;
import emu.grasscutter.utils.Position;
import java.util.List;
@Opcodes(PacketOpcodes.WidgetDoBagReq)
public class HandlerWidgetDoBagReq extends PacketHandler {
@Override
public void handle(GameSession session, byte[] header, byte[] payload) throws Exception {
WidgetDoBagReqOuterClass.WidgetDoBagReq req = WidgetDoBagReqOuterClass.WidgetDoBagReq.parseFrom(payload);
switch (req.getMaterialId()) {
case 220026 -> {
GadgetData gadgetData = GameData.getGadgetDataMap().get(70500025);
Position pos = new Position(req.getWidgetCreatorInfo().getLocationInfo().getPos());
Position rot = new Position(req.getWidgetCreatorInfo().getLocationInfo().getRot());
GameEntity entity = new EntityVehicle(
session.getPlayer().getScene(),
session.getPlayer(),
gadgetData.getId(),
0,
pos,
rot
);
session.getPlayer().getScene().addEntity(entity);
session.send(new PacketWidgetGadgetDataNotify(70500025, List.of(entity.getId()))); // ???
session.send(new PacketWidgetCoolDownNotify(15, System.currentTimeMillis() + 5000L, true));
session.send(new PacketWidgetCoolDownNotify(15, System.currentTimeMillis() + 5000L, true));
// Send twice, and I don't know why, Ask mhy
session.send(new PacketWidgetDoBagRsp());
}
default -> {
session.send(new PacketWidgetDoBagRsp());
}
}
}
}
package emu.grasscutter.server.packet.send;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.net.packet.BasePacket;
import emu.grasscutter.net.packet.PacketOpcodes;
import emu.grasscutter.net.proto.AllWidgetDataNotifyOuterClass.AllWidgetDataNotify;
import emu.grasscutter.net.proto.LunchBoxDataOuterClass;
import emu.grasscutter.net.proto.WidgetSlotDataOuterClass;
import emu.grasscutter.net.proto.WidgetSlotTagOuterClass;
import java.util.List;
import java.util.Map;
public class PacketAllWidgetDataNotify extends BasePacket {
public PacketAllWidgetDataNotify(Player player) {
super(PacketOpcodes.AllWidgetDataNotify);
// TODO: Implement this
AllWidgetDataNotify.Builder proto = AllWidgetDataNotify.newBuilder()
// If you want to implement this, feel free to do so. :)
.setLunchBoxData(
LunchBoxDataOuterClass.LunchBoxData.newBuilder().build()
)
// Maybe it's a little difficult, or it makes you upset :(
.addAllOneoffGatherPointDetectorDataList(List.of())
// So, goodbye, and hopefully sometime in the future o(* ̄▽ ̄*)ブ
.addAllCoolDownGroupDataList(List.of())
// I'll see your PR with a title that says (・∀・(・∀・(・∀・*)
.addAllAnchorPointList(List.of())
// "Complete implementation of widget functionality" b( ̄▽ ̄)d 
.addAllClientCollectorDataList(List.of())
// Good luck, my boy.
.addAllNormalCoolDownDataList(List.of());
if (player.getWidgetId() == null) {
proto.addAllSlotList(List.of());
} else {
proto.addSlotList(
WidgetSlotDataOuterClass.WidgetSlotData.newBuilder()
.setIsActive(true)
.setMaterialId(player.getWidgetId())
.build()
);
proto.addSlotList(
WidgetSlotDataOuterClass.WidgetSlotData.newBuilder()
.setTag(WidgetSlotTagOuterClass.WidgetSlotTag.WIDGET_SLOT_ATTACH_AVATAR)
.build()
);
}
AllWidgetDataNotify protoData = proto.build();
this.setData(protoData);
}
}
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment