Commit e6402c31 authored by memetrollsXD's avatar memetrollsXD
Browse files

Merge branch 'stable' into development

parents d5d90564 1dfe8733
package emu.grasscutter.game.avatar;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
......@@ -13,6 +15,7 @@ import dev.morphia.annotations.Indexed;
import dev.morphia.annotations.PostLoad;
import dev.morphia.annotations.PrePersist;
import dev.morphia.annotations.Transient;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.data.GenshinData;
import emu.grasscutter.data.common.FightPropData;
import emu.grasscutter.data.custom.OpenConfigEntry;
......@@ -38,10 +41,13 @@ import emu.grasscutter.game.inventory.EquipType;
import emu.grasscutter.game.inventory.GenshinItem;
import emu.grasscutter.game.props.ElementType;
import emu.grasscutter.game.props.EntityIdType;
import emu.grasscutter.game.props.FetterState;
import emu.grasscutter.game.props.FightProperty;
import emu.grasscutter.game.props.PlayerProperty;
import emu.grasscutter.net.proto.AvatarFetterInfoOuterClass.AvatarFetterInfo;
import emu.grasscutter.net.proto.FetterDataOuterClass.FetterData;
import emu.grasscutter.net.proto.AvatarInfoOuterClass.AvatarInfo;
import emu.grasscutter.server.packet.send.PacketAbilityChangeNotify;
import emu.grasscutter.server.packet.send.PacketAvatarEquipChangeNotify;
import emu.grasscutter.server.packet.send.PacketAvatarFightPropNotify;
import emu.grasscutter.utils.ProtoHelper;
......@@ -69,8 +75,10 @@ public class GenshinAvatar {
@Transient private final Int2ObjectMap<GenshinItem> equips;
@Transient private final Int2FloatOpenHashMap fightProp;
@Transient private final Set<String> bonusAbilityList;
@Transient private Set<String> extraAbilityEmbryos;
private List<Integer> fetters;
private Map<Integer, Integer> skillLevelMap; // Talent levels
private Map<Integer, Integer> proudSkillBonusMap; // Talent bonus levels (from const)
private int skillDepotId;
......@@ -86,8 +94,9 @@ public class GenshinAvatar {
// Morhpia only!
this.equips = new Int2ObjectOpenHashMap<>();
this.fightProp = new Int2FloatOpenHashMap();
this.bonusAbilityList = new HashSet<>();
this.proudSkillBonusMap = new HashMap<>(); // TODO Move to genshin avatar
this.extraAbilityEmbryos = new HashSet<>();
this.proudSkillBonusMap = new HashMap<>();
this.fetters = new ArrayList<>(); // TODO Move to genshin avatar
}
// On creation
......@@ -260,8 +269,16 @@ public class GenshinAvatar {
return proudSkillBonusMap;
}
public Set<String> getBonusAbilityList() {
return bonusAbilityList;
public Set<String> getExtraAbilityEmbryos() {
return extraAbilityEmbryos;
}
public void setFetterList(List<Integer> fetterList) {
this.fetters = fetterList;
}
public List<Integer> getFetterList() {
return fetters;
}
public float getCurrentHp() {
......@@ -347,14 +364,14 @@ public class GenshinAvatar {
item.setEquipCharacter(this.getAvatarId());
item.save();
if (this.getPlayer().hasSentAvatarDataNotify()) {
this.getPlayer().sendPacket(new PacketAvatarEquipChangeNotify(this, item));
}
if (shouldRecalc) {
this.recalcStats();
}
if (this.getPlayer().hasSentAvatarDataNotify()) {
this.getPlayer().sendPacket(new PacketAvatarEquipChangeNotify(this, item));
}
return true;
}
......@@ -371,11 +388,21 @@ public class GenshinAvatar {
}
public void recalcStats() {
recalcStats(false);
}
public void recalcStats(boolean forceSendAbilityChange) {
// Setup
AvatarData data = this.getAvatarData();
AvatarPromoteData promoteData = GenshinData.getAvatarPromoteData(data.getAvatarPromoteId(), this.getPromoteLevel());
Int2IntOpenHashMap setMap = new Int2IntOpenHashMap();
this.getBonusAbilityList().clear();
// Extra ability embryos
Set<String> prevExtraAbilityEmbryos = this.getExtraAbilityEmbryos();
this.extraAbilityEmbryos = new HashSet<>();
// Fetters
this.setFetterList(data.getFetters());
// Get hp percent, set to 100% if none
float hpPercent = this.getFightProperty(FightProperty.FIGHT_PROP_MAX_HP) <= 0 ? 1f : this.getFightProperty(FightProperty.FIGHT_PROP_CUR_HP) / this.getFightProperty(FightProperty.FIGHT_PROP_MAX_HP);
......@@ -458,7 +485,7 @@ public class GenshinAvatar {
}
// Add any skill strings from this affix
this.addToAbilityList(affix.getOpenConfig(), true);
this.addToExtraAbilityEmbryos(affix.getOpenConfig(), true);
} else {
break;
}
......@@ -505,7 +532,7 @@ public class GenshinAvatar {
}
// Add any skill strings from this affix
this.addToAbilityList(affix.getOpenConfig(), true);
this.addToExtraAbilityEmbryos(affix.getOpenConfig(), true);
}
}
}
......@@ -538,7 +565,7 @@ public class GenshinAvatar {
}
// Add any skill strings from this proud skill
this.addToAbilityList(proudSkillData.getOpenConfig(), true);
this.addToExtraAbilityEmbryos(proudSkillData.getOpenConfig(), true);
}
// Constellations
......@@ -550,7 +577,7 @@ public class GenshinAvatar {
}
// Add any skill strings from this constellation
this.addToAbilityList(avatarTalentData.getOpenConfig(), false);
this.addToExtraAbilityEmbryos(avatarTalentData.getOpenConfig(), false);
}
}
......@@ -573,11 +600,17 @@ public class GenshinAvatar {
// Packet
if (getPlayer() != null && getPlayer().hasSentAvatarDataNotify()) {
// Update stats for client
getPlayer().sendPacket(new PacketAvatarFightPropNotify(this));
// Update client abilities
EntityAvatar entity = this.getAsEntity();
if (entity != null && (!this.getExtraAbilityEmbryos().equals(prevExtraAbilityEmbryos) || forceSendAbilityChange)) {
getPlayer().sendPacket(new PacketAbilityChangeNotify(entity));
}
}
}
public void addToAbilityList(String openConfig, boolean forceAdd) {
public void addToExtraAbilityEmbryos(String openConfig, boolean forceAdd) {
if (openConfig == null || openConfig.length() == 0) {
return;
}
......@@ -586,14 +619,14 @@ public class GenshinAvatar {
if (entry == null) {
if (forceAdd) {
// Add config string to ability skill list anyways
this.getBonusAbilityList().add(openConfig);
this.getExtraAbilityEmbryos().add(openConfig);
}
return;
}
if (entry.getAddAbilities() != null) {
for (String ability : entry.getAddAbilities()) {
this.getBonusAbilityList().add(ability);
this.getExtraAbilityEmbryos().add(ability);
}
}
}
......@@ -668,6 +701,20 @@ public class GenshinAvatar {
}
public AvatarInfo toProto() {
AvatarFetterInfo.Builder avatarFetter = AvatarFetterInfo.newBuilder()
.setExpLevel(10)
.setExpNumber(6325); // Highest Level
if (this.getFetterList() != null) {
for (int i = 0; i < this.getFetterList().size(); i++) {
avatarFetter.addFetterList(
FetterData.newBuilder()
.setFetterId(this.getFetterList().get(i))
.setFetterState(FetterState.FINISH.getValue())
);
}
}
AvatarInfo.Builder avatarInfo = AvatarInfo.newBuilder()
.setAvatarId(this.getAvatarId())
.setGuid(this.getGuid())
......@@ -681,7 +728,7 @@ public class GenshinAvatar {
.putAllProudSkillExtraLevel(getProudSkillBonusMap())
.setAvatarType(1)
.setBornTime(this.getBornTime())
.setFetterInfo(AvatarFetterInfo.newBuilder().setExpLevel(1))
.setFetterInfo(avatarFetter)
.setWearingFlycloakId(this.getFlyCloak())
.setCostumeId(this.getCostume());
......
......@@ -223,8 +223,8 @@ public class EntityAvatar extends GenshinEntity {
}
}
// Add equip abilities
if (this.getAvatar().getBonusAbilityList().size() > 0) {
for (String skill : this.getAvatar().getBonusAbilityList()) {
if (this.getAvatar().getExtraAbilityEmbryos().size() > 0) {
for (String skill : this.getAvatar().getExtraAbilityEmbryos()) {
AbilityEmbryo emb = AbilityEmbryo.newBuilder()
.setAbilityId(++embryoId)
.setAbilityNameHash(Utils.abilityHash(skill))
......
......@@ -220,7 +220,7 @@ public class FriendsList {
friendship.setOwner(getPlayer());
// Check if friend is online
GenshinPlayer friend = getPlayer().getSession().getServer().getPlayerByUid(friendship.getFriendProfile().getId());
GenshinPlayer friend = getPlayer().getSession().getServer().getPlayerByUid(friendship.getFriendProfile().getUid());
if (friend != null) {
// Set friend to online mode
friendship.setFriendProfile(friend);
......
......@@ -88,7 +88,7 @@ public class Friendship {
public FriendBrief toProto() {
FriendBrief proto = FriendBrief.newBuilder()
.setUid(getFriendProfile().getId())
.setUid(getFriendProfile().getUid())
.setNickname(getFriendProfile().getName())
.setLevel(getFriendProfile().getPlayerLevel())
.setAvatar(HeadImage.newBuilder().setAvatarId(getFriendProfile().getAvatarId()))
......
......@@ -7,7 +7,7 @@ import emu.grasscutter.utils.Utils;
public class PlayerProfile {
@Transient private GenshinPlayer player;
private int id;
@AlsoLoad("id") private int uid;
private int nameCard;
private int avatarId;
private String name;
......@@ -22,12 +22,12 @@ public class PlayerProfile {
public PlayerProfile() { }
public PlayerProfile(GenshinPlayer player) {
this.id = player.getUid();
this.uid = player.getUid();
this.syncWithCharacter(player);
}
public int getId() {
return id;
public int getUid() {
return uid;
}
public GenshinPlayer getPlayer() {
......@@ -87,6 +87,7 @@ public class PlayerProfile {
return;
}
this.uid = player.getUid();
this.name = player.getNickname();
this.avatarId = player.getHeadImage();
this.signature = player.getSignature();
......
......@@ -92,7 +92,7 @@ public class GachaBanner {
}
public GachaInfo toProto() {
String record = "http://" + (Grasscutter.getConfig().DispatchServerPublicIp.isEmpty() ? Grasscutter.getConfig().DispatchServerIp : Grasscutter.getConfig().DispatchServerPublicIp) + "/gacha";
String record = "http://" + (Grasscutter.getConfig().getDispatchOptions().PublicIp.isEmpty() ? Grasscutter.getConfig().getDispatchOptions().Ip : Grasscutter.getConfig().getDispatchOptions().PublicIp) + "/gacha";
GachaInfo.Builder info = GachaInfo.newBuilder()
.setGachaType(this.getGachaType())
......
package emu.grasscutter.game.gacha;
import java.io.File;
import java.io.FileReader;
import java.nio.file.*;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
......@@ -8,6 +10,7 @@ import java.util.concurrent.ThreadLocalRandom;
import com.google.gson.reflect.TypeToken;
import com.sun.nio.file.SensitivityWatchEventModifier;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.data.GenshinData;
import emu.grasscutter.data.def.ItemData;
......@@ -21,17 +24,20 @@ import emu.grasscutter.net.proto.GachaTransferItemOuterClass.GachaTransferItem;
import emu.grasscutter.net.proto.GetGachaInfoRspOuterClass.GetGachaInfoRsp;
import emu.grasscutter.net.proto.ItemParamOuterClass.ItemParam;
import emu.grasscutter.server.game.GameServer;
import emu.grasscutter.server.game.GameServerTickEvent;
import emu.grasscutter.server.packet.send.PacketDoGachaRsp;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import it.unimi.dsi.fastutil.ints.IntArrayList;
import it.unimi.dsi.fastutil.ints.IntList;
import org.greenrobot.eventbus.Subscribe;
public class GachaManager {
private final GameServer server;
private final Int2ObjectMap<GachaBanner> gachaBanners;
private GetGachaInfoRsp cachedProto;
WatchService watchService;
private int[] yellowAvatars = new int[] {1003, 1016, 1042, 1035, 1041};
private int[] yellowWeapons = new int[] {11501, 11502, 12501, 12502, 13502, 13505, 14501, 14502, 15501, 15502};
private int[] purpleAvatars = new int[] {1006, 1014, 1015, 1020, 1021, 1023, 1024, 1025, 1027, 1031, 1032, 1034, 1036, 1039, 1043, 1044, 1045, 1048, 1053, 1055, 1056, 1064};
......@@ -40,11 +46,12 @@ public class GachaManager {
private static int starglitterId = 221;
private static int stardustId = 222;
public GachaManager(GameServer server) {
this.server = server;
this.gachaBanners = new Int2ObjectOpenHashMap<>();
this.load();
this.startWatcher(server);
}
public GameServer getServer() {
......@@ -65,9 +72,16 @@ public class GachaManager {
public synchronized void load() {
try (FileReader fileReader = new FileReader(Grasscutter.getConfig().DATA_FOLDER + "Banners.json")) {
getGachaBanners().clear();
List<GachaBanner> banners = Grasscutter.getGsonFactory().fromJson(fileReader, TypeToken.getParameterized(Collection.class, GachaBanner.class).getType());
for (GachaBanner banner : banners) {
getGachaBanners().put(banner.getGachaType(), banner);
if(banners.size() > 0) {
for (GachaBanner banner : banners) {
getGachaBanners().put(banner.getGachaType(), banner);
}
Grasscutter.getLogger().info("Banners successfully loaded.");
this.cachedProto = createProto();
} else {
Grasscutter.getLogger().error("Unable to load banners. Banners size is 0.");
}
} catch (Exception e) {
// TODO Auto-generated catch block
......@@ -204,7 +218,6 @@ public class GachaManager {
addStarglitter = 2;
// Add 1 const
gachaItem.addTransferItems(GachaTransferItem.newBuilder().setItem(ItemParam.newBuilder().setItemId(constItemId).setCount(1)).setIsTransferItemNew(constItem == null));
gachaItem.addTokenItemList(ItemParam.newBuilder().setItemId(constItemId).setCount(1));
player.getInventory().addItem(constItemId, 1);
} else {
// Is max const
......@@ -266,6 +279,48 @@ public class GachaManager {
// Packets
player.sendPacket(new PacketDoGachaRsp(banner, list));
}
private synchronized void startWatcher(GameServer server) {
if(this.watchService == null) {
try {
this.watchService = FileSystems.getDefault().newWatchService();
Path path = new File(Grasscutter.getConfig().DATA_FOLDER).toPath();
path.register(watchService, new WatchEvent.Kind[]{StandardWatchEventKinds.ENTRY_MODIFY}, SensitivityWatchEventModifier.HIGH);
server.OnGameServerTick.register(this);
} catch (Exception e) {
Grasscutter.getLogger().error("Unable to load the Gacha Manager Watch Service. If ServerOptions.watchGacha is true it will not auto-reload");
e.printStackTrace();
}
} else {
Grasscutter.getLogger().error("Cannot reinitialise watcher ");
}
}
@Subscribe
public synchronized void watchBannerJson(GameServerTickEvent tickEvent) {
if(Grasscutter.getConfig().getGameServerOptions().WatchGacha) {
try {
WatchKey watchKey = watchService.take();
for (WatchEvent<?> event : watchKey.pollEvents()) {
final Path changed = (Path) event.context();
if (changed.endsWith("Banners.json")) {
Grasscutter.getLogger().info("Change detected with banners.json. Reloading gacha config");
this.load();
}
}
boolean valid = watchKey.reset();
if (!valid) {
Grasscutter.getLogger().error("Unable to reset Gacha Manager Watch Key. Auto-reload of banners.json will no longer work.");
return;
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
private synchronized GetGachaInfoRsp createProto() {
GetGachaInfoRsp.Builder proto = GetGachaInfoRsp.newBuilder().setGachaRandom(12345);
......
......@@ -37,10 +37,10 @@ public class Inventory implements Iterable<GenshinItem> {
this.store = new Long2ObjectOpenHashMap<>();
this.inventoryTypes = new Int2ObjectOpenHashMap<>();
this.createInventoryTab(ItemType.ITEM_WEAPON, new EquipInventoryTab(Grasscutter.getConfig().getServerOptions().InventoryLimitWeapon));
this.createInventoryTab(ItemType.ITEM_RELIQUARY, new EquipInventoryTab(Grasscutter.getConfig().getServerOptions().InventoryLimitRelic));
this.createInventoryTab(ItemType.ITEM_MATERIAL, new MaterialInventoryTab(Grasscutter.getConfig().getServerOptions().InventoryLimitMaterial));
this.createInventoryTab(ItemType.ITEM_FURNITURE, new MaterialInventoryTab(Grasscutter.getConfig().getServerOptions().InventoryLimitFurniture));
this.createInventoryTab(ItemType.ITEM_WEAPON, new EquipInventoryTab(Grasscutter.getConfig().getGameServerOptions().InventoryLimitWeapon));
this.createInventoryTab(ItemType.ITEM_RELIQUARY, new EquipInventoryTab(Grasscutter.getConfig().getGameServerOptions().InventoryLimitRelic));
this.createInventoryTab(ItemType.ITEM_MATERIAL, new MaterialInventoryTab(Grasscutter.getConfig().getGameServerOptions().InventoryLimitMaterial));
this.createInventoryTab(ItemType.ITEM_FURNITURE, new MaterialInventoryTab(Grasscutter.getConfig().getGameServerOptions().InventoryLimitFurniture));
}
public GenshinPlayer getPlayer() {
......
package emu.grasscutter.game.managers;
import emu.grasscutter.commands.CommandMap;
import emu.grasscutter.command.CommandMap;
import emu.grasscutter.game.GenshinPlayer;
import emu.grasscutter.net.packet.GenshinPacket;
import emu.grasscutter.server.game.GameServer;
......
......@@ -471,7 +471,7 @@ public class InventoryManager {
}
// Consume weapon
player.getInventory().removeItem(feed);
player.getInventory().removeItem(feed, 1);
// Get
weapon.setRefinement(targetRefineLevel);
......@@ -589,7 +589,6 @@ public class InventoryManager {
// Update proud skills
AvatarSkillDepotData skillDepot = GenshinData.getAvatarSkillDepotDataMap().get(avatar.getSkillDepotId());
boolean hasAddedProudSkill = false;
if (skillDepot != null && skillDepot.getInherentProudSkillOpens() != null) {
for (InherentProudSkillOpens openData : skillDepot.getInherentProudSkillOpens()) {
......@@ -599,7 +598,6 @@ public class InventoryManager {
if (openData.getNeedAvatarPromoteLevel() == avatar.getPromoteLevel()) {
int proudSkillId = (openData.getProudSkillGroupId() * 100) + 1;
if (GenshinData.getProudSkillDataMap().containsKey(proudSkillId)) {
hasAddedProudSkill = true;
avatar.getProudSkillList().add(proudSkillId);
player.sendPacket(new PacketProudSkillChangeNotify(avatar));
}
......@@ -607,20 +605,13 @@ public class InventoryManager {
}
}
// Racalc stats and save avatar
avatar.recalcStats();
avatar.save();
// Resend ability embryos if proud skill has been added
if (hasAddedProudSkill && avatar.getAsEntity() != null) {
player.sendPacket(new PacketAbilityChangeNotify(avatar.getAsEntity()));
}
// TODO Send entity prop update packet to world
// Packets
player.sendPacket(new PacketAvatarPropNotify(avatar));
player.sendPacket(new PacketAvatarPromoteRsp(avatar));
// TODO Send entity prop update packet to world
avatar.recalcStats(true);
avatar.save();
}
public void upgradeAvatar(GenshinPlayer player, long guid, int itemId, int count) {
......@@ -804,6 +795,12 @@ public class InventoryManager {
// Get talent
int currentTalentLevel = avatar.getCoreProudSkillLevel();
int nextTalentId = ((avatar.getAvatarId() % 10000000) * 10) + currentTalentLevel + 1;
if (avatar.getAvatarId() == 10000006) {
// Lisa is special in that her talentId starts with 4 instead of 6.
nextTalentId = 40 + currentTalentLevel + 1;
}
AvatarTalentData talentData = GenshinData.getAvatarTalentDataMap().get(nextTalentId);
if (talentData == null) {
......@@ -821,25 +818,20 @@ public class InventoryManager {
// Apply + recalc
avatar.getTalentIdList().add(talentData.getId());
avatar.setCoreProudSkillLevel(currentTalentLevel + 1);
avatar.recalcStats();
// Packet
player.sendPacket(new PacketAvatarUnlockTalentNotify(avatar, nextTalentId));
player.sendPacket(new PacketUnlockAvatarTalentRsp(avatar, nextTalentId));
// Proud skill bonus map
// Proud skill bonus map (Extra skills)
OpenConfigEntry entry = GenshinData.getOpenConfigEntries().get(talentData.getOpenConfig());
if (entry != null && entry.getExtraTalentIndex() > 0) {
avatar.recalcProudSkillBonusMap();
player.sendPacket(new PacketProudSkillExtraLevelNotify(avatar, entry.getExtraTalentIndex()));
}
// Resend ability embryos
if (avatar.getAsEntity() != null) {
player.sendPacket(new PacketAbilityChangeNotify(avatar.getAsEntity()));
}
// Save avatar
// Recalc + save avatar
avatar.recalcStats(true);
avatar.save();
}
......
package emu.grasscutter.game.props;
import java.util.HashMap;
import java.util.Map;
import java.util.stream.Stream;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
public enum FetterState {
NONE(0),
NOT_OPEN(1),
OPEN(1),
FINISH(3);
private final int value;
private static final Int2ObjectMap<FetterState> map = new Int2ObjectOpenHashMap<>();
private static final Map<String, FetterState> stringMap = new HashMap<>();
static {
Stream.of(values()).forEach(e -> {
map.put(e.getValue(), e);
stringMap.put(e.name(), e);
});
}
private FetterState(int value) {
this.value = value;
}
public int getValue() {
return value;
}
public static FetterState getTypeByValue(int value) {
return map.getOrDefault(value, NONE);
}
public static FetterState getTypeByName(String name) {
return stringMap.getOrDefault(name, NONE);
}
}
package emu.grasscutter.server.dispatch;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.net.InetSocketAddress;
import java.net.URI;
import java.net.URLDecoder;
import java.security.KeyStore;
import java.util.Base64;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.protobuf.ByteString;
import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpHandler;
import com.sun.net.httpserver.HttpServer;
import com.sun.net.httpserver.HttpsConfigurator;
import com.sun.net.httpserver.HttpsServer;
import emu.grasscutter.Config;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.database.DatabaseHelper;
import emu.grasscutter.game.Account;
......@@ -32,30 +16,34 @@ import emu.grasscutter.net.proto.QueryCurrRegionHttpRspOuterClass.QueryCurrRegio
import emu.grasscutter.net.proto.QueryRegionListHttpRspOuterClass.QueryRegionListHttpRsp;
import emu.grasscutter.net.proto.RegionInfoOuterClass.RegionInfo;
import emu.grasscutter.net.proto.RegionSimpleInfoOuterClass.RegionSimpleInfo;
import emu.grasscutter.server.dispatch.json.ComboTokenReqJson;
import emu.grasscutter.server.dispatch.json.ComboTokenResJson;
import emu.grasscutter.server.dispatch.json.LoginAccountRequestJson;
import emu.grasscutter.server.dispatch.json.LoginResultJson;
import emu.grasscutter.server.dispatch.json.LoginTokenRequestJson;
import emu.grasscutter.server.dispatch.json.*;
import emu.grasscutter.server.dispatch.json.ComboTokenReqJson.LoginTokenData;
import emu.grasscutter.utils.FileUtils;
import emu.grasscutter.utils.Utils;
import com.sun.net.httpserver.HttpServer;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import java.io.*;
import java.net.InetSocketAddress;
import java.net.URI;
import java.net.URLDecoder;
import java.security.KeyStore;
import java.util.*;
public final class DispatchServer {
public static String query_region_list = "";
public static String query_cur_region = "";
private final InetSocketAddress address;
private final Gson gson;
private QueryCurrRegionHttpRsp currRegion;
private final String defaultServerName = "os_usa";
public String regionListBase64;
public String regionCurrentBase64;
public static String query_region_list = "";
public static String query_cur_region = "";
public HashMap<String, RegionData> regions;
public DispatchServer() {
this.address = new InetSocketAddress(Grasscutter.getConfig().DispatchServerIp, Grasscutter.getConfig().DispatchServerPort);
this.regions = new HashMap<String, RegionData>();
this.address = new InetSocketAddress(Grasscutter.getConfig().getDispatchOptions().Ip, Grasscutter.getConfig().getDispatchOptions().Port);
this.gson = new GsonBuilder().create();
this.loadQueries();
......@@ -71,7 +59,13 @@ public final class DispatchServer {
}
public QueryCurrRegionHttpRsp getCurrRegion() {
return currRegion;
// Needs to be fixed by having the game servers connect to the dispatch server.
if(Grasscutter.getConfig().RunMode.equalsIgnoreCase("HYBRID")) {
return regions.get(defaultServerName).parsedRegionQuery;
}
Grasscutter.getLogger().warn("[Dispatch] Unsupported run mode for getCurrRegion()");
return null;
}
public void loadQueries() {
......@@ -81,14 +75,14 @@ public final class DispatchServer {
if (file.exists()) {
query_region_list = new String(FileUtils.read(file));
} else {
Grasscutter.getLogger().warn("query_region_list not found! Using default region list.");
Grasscutter.getLogger().warn("[Dispatch] query_region_list not found! Using default region list.");
}
file = new File(Grasscutter.getConfig().DATA_FOLDER + "query_cur_region.txt");
if (file.exists()) {
query_cur_region = new String(FileUtils.read(file));
} else {
Grasscutter.getLogger().warn("query_cur_region not found! Using default current region.");
Grasscutter.getLogger().warn("[Dispatch] query_cur_region not found! Using default current region.");
}
}
......@@ -99,53 +93,80 @@ public final class DispatchServer {
byte[] decoded2 = Base64.getDecoder().decode(query_cur_region);
QueryCurrRegionHttpRsp regionQuery = QueryCurrRegionHttpRsp.parseFrom(decoded2);
RegionSimpleInfo server = RegionSimpleInfo.newBuilder()
.setName("os_usa")
.setTitle(Grasscutter.getConfig().GameServerName)
.setType("DEV_PUBLIC")
.setDispatchUrl("https://" + (Grasscutter.getConfig().DispatchServerPublicIp.isEmpty() ? Grasscutter.getConfig().DispatchServerIp : Grasscutter.getConfig().DispatchServerPublicIp) + ":" + getAddress().getPort() + "/query_cur_region")
.build();
RegionSimpleInfo serverTest2 = RegionSimpleInfo.newBuilder()
.setName("os_euro")
.setTitle("Grasscutter")
.setType("DEV_PUBLIC")
.setDispatchUrl("https://" + (Grasscutter.getConfig().DispatchServerPublicIp.isEmpty() ? Grasscutter.getConfig().DispatchServerIp : Grasscutter.getConfig().DispatchServerPublicIp) + ":" + getAddress().getPort() + "/query_cur_region")
.build();
List<RegionSimpleInfo> servers = new ArrayList<RegionSimpleInfo>();
List<String> usedNames = new ArrayList<String>(); // List to check for potential naming conflicts
if(Grasscutter.getConfig().RunMode.equalsIgnoreCase("HYBRID")) { // Automatically add the game server if in hybrid mode
RegionSimpleInfo server = RegionSimpleInfo.newBuilder()
.setName("os_usa")
.setTitle(Grasscutter.getConfig().getGameServerOptions().Name)
.setType("DEV_PUBLIC")
.setDispatchUrl("https://" + (Grasscutter.getConfig().getDispatchOptions().PublicIp.isEmpty() ? Grasscutter.getConfig().getDispatchOptions().Ip : Grasscutter.getConfig().getDispatchOptions().PublicIp) + ":" + getAddress().getPort() + "/query_cur_region_" + defaultServerName)
.build();
usedNames.add(defaultServerName);
servers.add(server);
RegionInfo serverRegion = regionQuery.getRegionInfo().toBuilder()
.setIp((Grasscutter.getConfig().getGameServerOptions().PublicIp.isEmpty() ? Grasscutter.getConfig().getGameServerOptions().Ip : Grasscutter.getConfig().getGameServerOptions().PublicIp))
.setPort(Grasscutter.getConfig().getGameServerOptions().Port)
.setSecretKey(ByteString.copyFrom(FileUtils.read(Grasscutter.getConfig().KEY_FOLDER + "dispatchSeed.bin")))
.build();
QueryCurrRegionHttpRsp parsedRegionQuery = regionQuery.toBuilder().setRegionInfo(serverRegion).build();
regions.put(defaultServerName, new RegionData(parsedRegionQuery, Base64.getEncoder().encodeToString(parsedRegionQuery.toByteString().toByteArray())));
} else {
if(Grasscutter.getConfig().getDispatchOptions().getGameServers().length == 0) {
Grasscutter.getLogger().error("[Dispatch] There are no game servers available. Exiting due to unplayable state.");
System.exit(1);
}
}
for (Config.DispatchServerOptions.RegionInfo regionInfo : Grasscutter.getConfig().getDispatchOptions().getGameServers()) {
if(usedNames.contains(regionInfo.Name)) {
Grasscutter.getLogger().error("Region name already in use.");
continue;
}
RegionSimpleInfo server = RegionSimpleInfo.newBuilder()
.setName(regionInfo.Name)
.setTitle(regionInfo.Title)
.setType("DEV_PUBLIC")
.setDispatchUrl("https://" + (Grasscutter.getConfig().getDispatchOptions().PublicIp.isEmpty() ? Grasscutter.getConfig().getDispatchOptions().Ip : Grasscutter.getConfig().getDispatchOptions().PublicIp) + ":" + getAddress().getPort() + "/query_cur_region_" + regionInfo.Name)
.build();
usedNames.add(regionInfo.Name);
servers.add(server);
RegionInfo serverRegion = regionQuery.getRegionInfo().toBuilder()
.setIp(regionInfo.Ip)
.setPort(regionInfo.Port)
.setSecretKey(ByteString.copyFrom(FileUtils.read(Grasscutter.getConfig().KEY_FOLDER + "dispatchSeed.bin")))
.build();
QueryCurrRegionHttpRsp parsedRegionQuery = regionQuery.toBuilder().setRegionInfo(serverRegion).build();
regions.put(regionInfo.Name, new RegionData(parsedRegionQuery, Base64.getEncoder().encodeToString(parsedRegionQuery.toByteString().toByteArray())));
}
QueryRegionListHttpRsp regionList = QueryRegionListHttpRsp.newBuilder()
.addServers(server)
.addServers(serverTest2)
.addAllServers(servers)
.setClientSecretKey(rl.getClientSecretKey())
.setClientCustomConfigEncrypted(rl.getClientCustomConfigEncrypted())
.setEnableLoginPc(true)
.build();
RegionInfo currentRegion = regionQuery.getRegionInfo().toBuilder()
.setIp((Grasscutter.getConfig().GameServerPublicIp.isEmpty() ? Grasscutter.getConfig().GameServerIp : Grasscutter.getConfig().GameServerPublicIp))
.setPort(Grasscutter.getConfig().GameServerPort)
.setSecretKey(ByteString.copyFrom(FileUtils.read(Grasscutter.getConfig().KEY_FOLDER + "dispatchSeed.bin")))
.build();
QueryCurrRegionHttpRsp parsedRegionQuery = regionQuery.toBuilder().setRegionInfo(currentRegion).build();
this.regionListBase64 = Base64.getEncoder().encodeToString(regionList.toByteString().toByteArray());
this.regionCurrentBase64 = Base64.getEncoder().encodeToString(parsedRegionQuery.toByteString().toByteArray());
this.currRegion = parsedRegionQuery;
} catch (Exception e) {
Grasscutter.getLogger().error("Error while initializing region info!", e);
Grasscutter.getLogger().error("[Dispatch] Error while initializing region info!", e);
}
}
public void start() throws Exception {
HttpServer server;
if(Grasscutter.getConfig().UseSSL) {
if (Grasscutter.getConfig().getDispatchOptions().UseSSL) {
HttpsServer httpsServer;
httpsServer = HttpsServer.create(getAddress(), 0);
SSLContext sslContext = SSLContext.getInstance("TLS");
try (FileInputStream fis = new FileInputStream(Grasscutter.getConfig().DispatchServerKeystorePath)) {
char[] keystorePassword = Grasscutter.getConfig().DispatchServerKeystorePassword.toCharArray();
try (FileInputStream fis = new FileInputStream(Grasscutter.getConfig().getDispatchOptions().KeystorePath)) {
char[] keystorePassword = Grasscutter.getConfig().getDispatchOptions().KeystorePassword.toCharArray();
KeyStore ks = KeyStore.getInstance("PKCS12");
ks.load(fis, keystorePassword);
KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
......@@ -156,56 +177,39 @@ public final class DispatchServer {
httpsServer.setHttpsConfigurator(new HttpsConfigurator(sslContext));
server = httpsServer;
} catch (Exception e) {
Grasscutter.getLogger().error("No SSL cert found!");
return;
Grasscutter.getLogger().warn("[Dispatch] No SSL cert found! Falling back to HTTP server.");
Grasscutter.getConfig().getDispatchOptions().UseSSL = false;
server = HttpServer.create(getAddress(), 0);
}
} else {
server = HttpServer.create(getAddress(), 0);
}
server.createContext("/", t -> {
//Create a response form the request query parameters
String response = "Hello";
//Set the response header status and length
t.getResponseHeaders().put("Content-Type", Collections.singletonList("text/html; charset=UTF-8"));
t.sendResponseHeaders(200, response.getBytes().length);
//Write the response string
OutputStream os = t.getResponseBody();
os.write(response.getBytes());
os.close();
});
server.createContext("/", t -> responseHTML(t, "Hello"));
// Dispatch
server.createContext("/query_region_list", t -> {
// Log
Grasscutter.getLogger().info("Client request: query_region_list");
// Create a response form the request query parameters
String response = regionListBase64;
// Set the response header status and length
t.getResponseHeaders().put("Content-Type", Collections.singletonList("text/html; charset=UTF-8"));
t.sendResponseHeaders(200, response.getBytes().length);
// Write the response string
OutputStream os = t.getResponseBody();
os.write(response.getBytes());
os.close();
});
server.createContext("/query_cur_region", t -> {
// Log
Grasscutter.getLogger().info("Client request: query_cur_region");
// Create a response form the request query parameters
URI uri = t.getRequestURI();
String response = "CAESGE5vdCBGb3VuZCB2ZXJzaW9uIGNvbmZpZw==";
if (uri.getQuery() != null && uri.getQuery().length() > 0) {
response = regionCurrentBase64;
}
// Set the response header status and length
t.getResponseHeaders().put("Content-Type", Collections.singletonList("text/html; charset=UTF-8"));
t.sendResponseHeaders(200, response.getBytes().length);
// Write the response string
OutputStream os = t.getResponseBody();
os.write(response.getBytes());
os.close();
Grasscutter.getLogger().info(String.format("[Dispatch] Client %s request: query_region_list", t.getRemoteAddress()));
responseHTML(t, regionListBase64);
});
for (String regionName : regions.keySet()) {
server.createContext("/query_cur_region_" + regionName, t -> {
String regionCurrentBase64 = regions.get(regionName).Base64;
// Log
Grasscutter.getLogger().info(String.format("Client %s request: query_cur_region_%s", t.getRemoteAddress(), regionName));
// Create a response form the request query parameters
URI uri = t.getRequestURI();
String response = "CAESGE5vdCBGb3VuZCB2ZXJzaW9uIGNvbmZpZw==";
if (uri.getQuery() != null && uri.getQuery().length() > 0) {
response = regionCurrentBase64;
}
responseHTML(t, response);
});
}
// Login via account
server.createContext("/hk4e_global/mdk/shield/api/login", t -> {
// Get post data
......@@ -213,38 +217,56 @@ public final class DispatchServer {
try {
String body = Utils.toString(t.getRequestBody());
requestData = getGsonFactory().fromJson(body, LoginAccountRequestJson.class);
} catch (Exception e) {
}
} catch (Exception ignored) { }
// Create response json
if (requestData == null) {
return;
}
LoginResultJson responseData = new LoginResultJson();
Grasscutter.getLogger().info(String.format("[Dispatch] Client %s is trying to log in", t.getRemoteAddress()));
// Login
Account account = DatabaseHelper.getAccountByName(requestData.account);
// Test
// Check if account exists, else create a new one.
if (account == null) {
responseData.retcode = -201;
responseData.message = "Username not found.";
// Account doesnt exist, so we can either auto create it if the config value is set
if (Grasscutter.getConfig().getDispatchOptions().AutomaticallyCreateAccounts) {
// This account has been created AUTOMATICALLY. There will be no permissions added.
account = DatabaseHelper.createAccountWithId(requestData.account, 0);
if (account != null) {
responseData.message = "OK";
responseData.data.account.uid = account.getId();
responseData.data.account.token = account.generateSessionKey();
responseData.data.account.email = account.getEmail();
Grasscutter.getLogger().info(String.format("[Dispatch] Client %s failed to log in: Account %s created", t.getRemoteAddress(), responseData.data.account.uid));
} else {
responseData.retcode = -201;
responseData.message = "Username not found, create failed.";
Grasscutter.getLogger().info(String.format("[Dispatch] Client %s failed to log in: Account create failed", t.getRemoteAddress()));
}
} else {
responseData.retcode = -201;
responseData.message = "Username not found.";
Grasscutter.getLogger().info(String.format("[Dispatch] Client %s failed to log in: Account no found", t.getRemoteAddress()));
}
} else {
// Account was found, log the player in
responseData.message = "OK";
responseData.data.account.uid = account.getId();
responseData.data.account.token = account.generateSessionKey();
responseData.data.account.email = account.getEmail();
Grasscutter.getLogger().info(String.format("[Dispatch] Client %s logged in as %s", t.getRemoteAddress(), responseData.data.account.uid));
}
// Create a response
String response = getGsonFactory().toJson(responseData);
// Set the response header status and length
t.getResponseHeaders().put("Content-Type", Collections.singletonList("application/json"));
t.sendResponseHeaders(200, response.getBytes().length);
// Write the response string
OutputStream os = t.getResponseBody();
os.write(response.getBytes());
os.close();
responseJSON(t, responseData);
});
// Login via token
server.createContext("/hk4e_global/mdk/shield/api/verify", t -> {
......@@ -253,14 +275,14 @@ public final class DispatchServer {
try {
String body = Utils.toString(t.getRequestBody());
requestData = getGsonFactory().fromJson(body, LoginTokenRequestJson.class);
} catch (Exception e) {
}
} catch (Exception ignored) { }
// Create response json
if (requestData == null) {
return;
}
LoginResultJson responseData = new LoginResultJson();
Grasscutter.getLogger().info(String.format("[Dispatch] Client %s is trying to log in via token", t.getRemoteAddress()));
// Login
Account account = DatabaseHelper.getAccountById(requestData.uid);
......@@ -269,22 +291,18 @@ public final class DispatchServer {
if (account == null || !account.getSessionKey().equals(requestData.token)) {
responseData.retcode = -111;
responseData.message = "Game account cache information error";
Grasscutter.getLogger().info(String.format("[Dispatch] Client %s failed to log in via token", t.getRemoteAddress()));
} else {
responseData.message = "OK";
responseData.data.account.uid = requestData.uid;
responseData.data.account.token = requestData.token;
responseData.data.account.email = account.getEmail();
Grasscutter.getLogger().info(String.format("[Dispatch] Client %s logged in via token as %s", t.getRemoteAddress(), responseData.data.account.uid));
}
// Create a response
String response = getGsonFactory().toJson(responseData);
// Set the response header status and length
t.getResponseHeaders().put("Content-Type", Collections.singletonList("application/json"));
t.sendResponseHeaders(200, response.getBytes().length);
// Write the response string
OutputStream os = t.getResponseBody();
os.write(response.getBytes());
os.close();
responseJSON(t, responseData);
});
// Exchange for combo token
server.createContext("/hk4e_global/combo/granter/login/v2/login", t -> {
......@@ -293,9 +311,8 @@ public final class DispatchServer {
try {
String body = Utils.toString(t.getRequestBody());
requestData = getGsonFactory().fromJson(body, ComboTokenReqJson.class);
} catch (Exception e) {
}
} catch (Exception ignored) { }
// Create response json
if (requestData == null || requestData.data == null) {
return;
......@@ -310,22 +327,18 @@ public final class DispatchServer {
if (account == null || !account.getSessionKey().equals(loginData.token)) {
responseData.retcode = -201;
responseData.message = "Wrong session key.";
Grasscutter.getLogger().info(String.format("[Dispatch] Client %s failed to exchange combo token", t.getRemoteAddress()));
} else {
responseData.message = "OK";
responseData.data.open_id = loginData.uid;
responseData.data.combo_id = "157795300";
responseData.data.combo_token = account.generateLoginToken();
Grasscutter.getLogger().info(String.format("[Dispatch] Client %s succeed to exchange combo token", t.getRemoteAddress()));
}
// Create a response
String response = getGsonFactory().toJson(responseData);
// Set the response header status and length
t.getResponseHeaders().put("Content-Type", Collections.singletonList("application/json"));
t.sendResponseHeaders(200, response.getBytes().length);
// Write the response string
OutputStream os = t.getResponseBody();
os.write(response.getBytes());
os.close();
responseJSON(t, responseData);
});
// Agreement and Protocol
server.createContext( // hk4e-sdk-os.hoyoverse.com
......@@ -389,63 +402,88 @@ public final class DispatchServer {
"/sdk/upload",
new DispatchHttpJsonHandler("{\"code\":0}")
);
// Start server
server.start();
Grasscutter.getLogger().info("Dispatch server started on port " + getAddress().getPort());
server.createContext( // /perf/config/verify?device_id=xxx&platform=x&name=xxx
"/perf/config/verify",
new DispatchHttpJsonHandler("{\"code\":0}")
);
// Logging servers
HttpServer overseaLogServer = HttpServer.create(new InetSocketAddress(Grasscutter.getConfig().DispatchServerIp, 8888), 0);
overseaLogServer.createContext( // overseauspider.yuanshen.com
"/log",
server.createContext( // overseauspider.yuanshen.com
"/log",
new DispatchHttpJsonHandler("{\"code\":0}")
);
overseaLogServer.start();
Grasscutter.getLogger().info("Log server (overseauspider) started on port " + 8888);
HttpServer uploadLogServer = HttpServer.create(new InetSocketAddress(Grasscutter.getConfig().DispatchServerIp, Grasscutter.getConfig().UploadLogPort), 0);
uploadLogServer.createContext( // log-upload-os.mihoyo.com
"/crash/dataUpload",
server.createContext( // log-upload-os.mihoyo.com
"/crash/dataUpload",
new DispatchHttpJsonHandler("{\"code\":0}")
);
uploadLogServer.createContext("/gacha", t -> {
//Create a response form the request query parameters
String response = "<!doctype html><html lang=\"en\"><head><title>Gacha</title></head><body></body></html>";
//Set the response header status and length
t.getResponseHeaders().put("Content-Type", Collections.singletonList("text/html; charset=UTF-8"));
t.sendResponseHeaders(200, response.getBytes().length);
//Write the response string
OutputStream os = t.getResponseBody();
os.write(response.getBytes());
os.close();
});
uploadLogServer.start();
Grasscutter.getLogger().info("Log server (log-upload-os) started on port " + Grasscutter.getConfig().UploadLogPort);
server.createContext("/gacha", t -> responseHTML(t, "<!doctype html><html lang=\"en\"><head><title>Gacha</title></head><body></body></html>"));
// Start server
server.start();
Grasscutter.getLogger().info("[Dispatch] Dispatch server started on port " + getAddress().getPort());
}
private void responseJSON(HttpExchange t, Object data) throws IOException {
// Create a response
String response = getGsonFactory().toJson(data);
// Set the response header status and length
t.getResponseHeaders().put("Content-Type", Collections.singletonList("application/json"));
t.sendResponseHeaders(200, response.getBytes().length);
// Write the response string
OutputStream os = t.getResponseBody();
os.write(response.getBytes());
os.close();
}
private void responseHTML(HttpExchange t, String response) throws IOException {
// Set the response header status and length
t.getResponseHeaders().put("Content-Type", Collections.singletonList("text/html; charset=UTF-8"));
t.sendResponseHeaders(200, response.getBytes().length);
//Write the response string
OutputStream os = t.getResponseBody();
os.write(response.getBytes());
os.close();
}
private Map<String, String> parseQueryString(String qs) {
Map<String, String> result = new HashMap<>();
if (qs == null)
return result;
int last = 0, next, l = qs.length();
while (last < l) {
next = qs.indexOf('&', last);
if (next == -1)
next = l;
if (next > last) {
int eqPos = qs.indexOf('=', last);
try {
if (eqPos < 0 || eqPos > next)
result.put(URLDecoder.decode(qs.substring(last, next), "utf-8"), "");
else
result.put(URLDecoder.decode(qs.substring(last, eqPos), "utf-8"), URLDecoder.decode(qs.substring(eqPos + 1, next), "utf-8"));
} catch (UnsupportedEncodingException e) {
throw new RuntimeException(e); // will never happen, utf-8 support is mandatory for java
}
}
last = next + 1;
}
return result;
Map<String, String> result = new HashMap<>();
if (qs == null) {
return result;
}
int last = 0, next, l = qs.length();
while (last < l) {
next = qs.indexOf('&', last);
if (next == -1) {
next = l;
}
if (next > last) {
int eqPos = qs.indexOf('=', last);
try {
if (eqPos < 0 || eqPos > next) {
result.put(URLDecoder.decode(qs.substring(last, next), "utf-8"), "");
} else {
result.put(URLDecoder.decode(qs.substring(last, eqPos), "utf-8"), URLDecoder.decode(qs.substring(eqPos + 1, next), "utf-8"));
}
} catch (UnsupportedEncodingException e) {
throw new RuntimeException(e); // will never happen, utf-8 support is mandatory for java
}
}
last = next + 1;
}
return result;
}
public static class RegionData {
QueryCurrRegionHttpRsp parsedRegionQuery;
String Base64;
public RegionData(QueryCurrRegionHttpRsp prq, String b64) {
this.parsedRegionQuery = prq;
this.Base64 = b64;
}
}
}
......@@ -7,7 +7,7 @@ public class ComboTokenReqJson {
public String device;
public String sign;
public class LoginTokenData {
public static class LoginTokenData {
public String uid;
public String token;
public boolean guest;
......
......@@ -5,7 +5,7 @@ public class ComboTokenResJson {
public int retcode;
public LoginData data = new LoginData();
public class LoginData {
public static class LoginData {
public int account_type = 1;
public boolean heartbeat;
public String combo_id;
......
......@@ -5,7 +5,7 @@ public class LoginResultJson {
public int retcode;
public VerifyData data = new VerifyData();
public class VerifyData {
public static class VerifyData {
public VerifyAccountData account = new VerifyAccountData();
public boolean device_grant_required = false;
public String realname_operation = "NONE";
......@@ -13,7 +13,7 @@ public class LoginResultJson {
public boolean safe_mobile_required = false;
}
public class VerifyAccountData {
public static class VerifyAccountData {
public String uid;
public String name = "";
public String email;
......
......@@ -6,7 +6,7 @@ import java.util.concurrent.ConcurrentHashMap;
import emu.grasscutter.GenshinConstants;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.commands.CommandMap;
import emu.grasscutter.command.CommandMap;
import emu.grasscutter.database.DatabaseHelper;
import emu.grasscutter.game.Account;
import emu.grasscutter.game.GenshinPlayer;
......@@ -19,6 +19,7 @@ import emu.grasscutter.game.shop.ShopManager;
import emu.grasscutter.net.packet.PacketHandler;
import emu.grasscutter.net.proto.SocialDetailOuterClass.SocialDetail;
import emu.grasscutter.netty.MihoyoKcpServer;
import org.greenrobot.eventbus.EventBus;
public final class GameServer extends MihoyoKcpServer {
private final InetSocketAddress address;
......@@ -33,10 +34,18 @@ public final class GameServer extends MihoyoKcpServer {
private final MultiplayerManager multiplayerManager;
private final DungeonManager dungeonManager;
private final CommandMap commandMap;
public EventBus OnGameServerStartFinish;
public EventBus OnGameServerTick;
public EventBus OnGameServerStop;
public GameServer(InetSocketAddress address) {
super(address);
OnGameServerStartFinish = EventBus.builder().throwSubscriberException(true).logNoSubscriberMessages(false).build();
OnGameServerTick = EventBus.builder().throwSubscriberException(true).logNoSubscriberMessages(false).build();
OnGameServerStop = EventBus.builder().throwSubscriberException(true).logNoSubscriberMessages(false).build();
this.setServerInitializer(new GameServerInitializer(this));
this.address = address;
this.packetHandler = new GameServerPacketHandler(PacketHandler.class);
......@@ -145,7 +154,7 @@ public final class GameServer extends MihoyoKcpServer {
public Account getAccountByName(String username) {
Optional<GenshinPlayer> playerOpt = getPlayers().values().stream().filter(player -> player.getAccount().getUsername().equals(username)).findFirst();
if (playerOpt.get() != null) {
if (playerOpt.isPresent()) {
return playerOpt.get().getAccount();
}
return DatabaseHelper.getAccountByName(username);
......@@ -155,14 +164,20 @@ public final class GameServer extends MihoyoKcpServer {
for (GenshinPlayer player : this.getPlayers().values()) {
player.onTick();
}
OnGameServerTick.post(new GameServerTickEvent());
}
@Override
public void onStartFinish() {
Grasscutter.getLogger().info("Game Server started on port " + address.getPort());
OnGameServerStartFinish.post(new GameServerStartFinishEvent());
}
public void onServerShutdown() {
OnGameServerStop.post(new GameServerStopEvent());
// Kick and save all players
List<GenshinPlayer> list = new ArrayList<>(this.getPlayers().size());
list.addAll(this.getPlayers().values());
......
......@@ -87,7 +87,7 @@ public class GameServerPacketHandler {
}
// Log unhandled packets
if (Grasscutter.getConfig().LOG_PACKETS) {
if (Grasscutter.getConfig().getGameServerOptions().LOG_PACKETS) {
//Grasscutter.getLogger().info("Unhandled packet (" + opcode + "): " + PacketOpcodesUtil.getOpcodeName(opcode));
}
}
......
package emu.grasscutter.server.game;
public class GameServerStartFinishEvent {
// Placeholder class for now, probably will get used later
}
package emu.grasscutter.server.game;
public class GameServerStopEvent {
// Placeholder class for now, probably will get used later
}
package emu.grasscutter.server.game;
public class GameServerTickEvent {
// Placeholder class for now, probably will get used later
}
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