Commit dce13cf6 authored by Magix's avatar Magix Committed by GitHub
Browse files

Merge branch 'development' into plugin-system

parents 832c460a b6fedcf2
...@@ -197,8 +197,8 @@ public final class SetStatsCommand implements CommandHandler { ...@@ -197,8 +197,8 @@ public final class SetStatsCommand implements CommandHandler {
float eelec = Integer.parseInt(args.get(1)); float eelec = Integer.parseInt(args.get(1));
EntityAvatar entity = sender.getTeamManager().getCurrentAvatarEntity(); EntityAvatar entity = sender.getTeamManager().getCurrentAvatarEntity();
float elec = eelec / 10000; float elec = eelec / 10000;
entity.setFightProperty(FightProperty.FIGHT_PROP_CRITICAL_HURT, elec); entity.setFightProperty(FightProperty.FIGHT_PROP_ELEC_ADD_HURT, elec);
entity.getWorld().broadcastPacket(new PacketEntityFightPropUpdateNotify(entity, FightProperty.FIGHT_PROP_CRITICAL_HURT)); entity.getWorld().broadcastPacket(new PacketEntityFightPropUpdateNotify(entity, FightProperty.FIGHT_PROP_ELEC_ADD_HURT));
float igelec = elec * 100; float igelec = elec * 100;
CommandHandler.sendMessage(sender, "Electro DMG Bonus set to " + igelec + "%"); CommandHandler.sendMessage(sender, "Electro DMG Bonus set to " + igelec + "%");
} catch (NumberFormatException ignored) { } catch (NumberFormatException ignored) {
......
...@@ -2,6 +2,7 @@ package emu.grasscutter.command.commands; ...@@ -2,6 +2,7 @@ package emu.grasscutter.command.commands;
import emu.grasscutter.command.Command; import emu.grasscutter.command.Command;
import emu.grasscutter.command.CommandHandler; import emu.grasscutter.command.CommandHandler;
import emu.grasscutter.data.def.AvatarSkillDepotData;
import emu.grasscutter.game.GenshinPlayer; import emu.grasscutter.game.GenshinPlayer;
import emu.grasscutter.game.avatar.GenshinAvatar; import emu.grasscutter.game.avatar.GenshinAvatar;
import emu.grasscutter.game.entity.EntityAvatar; import emu.grasscutter.game.entity.EntityAvatar;
...@@ -21,8 +22,9 @@ public class TalentCommand implements CommandHandler { ...@@ -21,8 +22,9 @@ public class TalentCommand implements CommandHandler {
return; return;
} }
if (args.size() < 0 || args.size() < 1){ if (args.size() < 1){
CommandHandler.sendMessage(sender, "To set talent level: /talent set <talentID> <value>"); CommandHandler.sendMessage(sender, "To set talent level: /talent set <talentID> <value>");
CommandHandler.sendMessage(sender, "Another way to set talent level: /talent <n or e or q> <value>");
CommandHandler.sendMessage(sender, "To get talent ID: /talent getid"); CommandHandler.sendMessage(sender, "To get talent ID: /talent getid");
return; return;
} }
...@@ -31,6 +33,7 @@ public class TalentCommand implements CommandHandler { ...@@ -31,6 +33,7 @@ public class TalentCommand implements CommandHandler {
switch (cmdSwitch) { switch (cmdSwitch) {
default: default:
CommandHandler.sendMessage(sender, "To set talent level: /talent set <talentID> <value>"); CommandHandler.sendMessage(sender, "To set talent level: /talent set <talentID> <value>");
CommandHandler.sendMessage(sender, "Another way to set talent level: /talent <n or e or q> <value>");
CommandHandler.sendMessage(sender, "To get talent ID: /talent getid"); CommandHandler.sendMessage(sender, "To get talent ID: /talent getid");
return; return;
case "set": case "set":
...@@ -90,6 +93,45 @@ public class TalentCommand implements CommandHandler { ...@@ -90,6 +93,45 @@ public class TalentCommand implements CommandHandler {
return; return;
} }
break;
case "n": case "e": case "q":
try {
EntityAvatar entity = sender.getTeamManager().getCurrentAvatarEntity();
GenshinAvatar avatar = entity.getAvatar();
AvatarSkillDepotData SkillDepot = avatar.getData().getSkillDepot();
int skillId;
switch (cmdSwitch) {
default:
skillId = SkillDepot.getSkills().get(0);
break;
case "e":
skillId = SkillDepot.getSkills().get(1);
break;
case "q":
skillId = SkillDepot.getEnergySkill();
break;
}
int nextLevel = Integer.parseInt(args.get(1));
int currentLevel = avatar.getSkillLevelMap().get(skillId);
if (args.size() < 1){
CommandHandler.sendMessage(sender, "To set talent level: /talent <n or e or q> <value>");
return;
}
if (nextLevel > 16){
CommandHandler.sendMessage(sender, "Invalid talent level. Level should be lower than 16");
return;
}
// Upgrade skill
avatar.getSkillLevelMap().put(skillId, nextLevel);
avatar.save();
// Packet
sender.sendPacket(new PacketAvatarSkillChangeNotify(avatar, skillId, currentLevel, nextLevel));
sender.sendPacket(new PacketAvatarSkillUpgradeRsp(avatar, skillId, currentLevel, nextLevel));
CommandHandler.sendMessage(sender, "Set this talent to " + nextLevel + ".");
} catch (NumberFormatException ignored) {
CommandHandler.sendMessage(sender, "Invalid talent level.");
return;
}
break; break;
case "getid": case "getid":
EntityAvatar entity = sender.getTeamManager().getCurrentAvatarEntity(); EntityAvatar entity = sender.getTeamManager().getCurrentAvatarEntity();
......
package emu.grasscutter.command.commands;
import emu.grasscutter.command.Command;
import emu.grasscutter.command.CommandHandler;
import emu.grasscutter.game.GenshinPlayer;
import emu.grasscutter.utils.Position;
import java.util.List;
@Command(label = "teleport", usage = "teleport <x> <y> <z>", aliases = {"tp"},
description = "Change the player's position.", permission = "player.teleport")
public class TelePortCommand implements CommandHandler {
@Override
public void execute(GenshinPlayer sender, List<String> args) {
if (sender == null) {
CommandHandler.sendMessage(null, "Run this command in-game.");
return;
}
if (args.size() < 3){
CommandHandler.sendMessage(sender, "Usage: /tp <x> <y> <z> [scene id]");
return;
}
try {
float x = 0f;
float y = 0f;
float z = 0f;
if (args.get(0).contains("~")) {
if (args.get(0).equals("~")) {
x = sender.getPos().getX();
} else {
x = Float.parseFloat(args.get(0).replace("~", "")) + sender.getPos().getX();
}
} else {
x = Float.parseFloat(args.get(0));
}
if (args.get(1).contains("~")) {
if (args.get(1).equals("~")) {
y = sender.getPos().getY();
} else {
y = Float.parseFloat(args.get(1).replace("~", "")) + sender.getPos().getY();
}
} else {
y = Float.parseFloat(args.get(1));
}
if (args.get(2).contains("~")) {
if (args.get(2).equals("~")) {
z = sender.getPos().getZ();
} else {
z = Float.parseFloat(args.get(2).replace("~", "")) + sender.getPos().getZ();
}
} else {
z = Float.parseFloat(args.get(2));
}
int sceneId = sender.getSceneId();
if (args.size() == 4){
sceneId = Integer.parseInt(args.get(3));
}
Position target = new Position(x, y, z);
boolean result = sender.getWorld().transferPlayerToScene(sender, sceneId, target);
if (!result) {
CommandHandler.sendMessage(sender, "Invalid position.");
}
} catch (NumberFormatException ignored) {
CommandHandler.sendMessage(sender, "Invalid position.");
}
}
}
...@@ -31,6 +31,7 @@ public class GenshinData { ...@@ -31,6 +31,7 @@ public class GenshinData {
private static final Int2ObjectMap<AvatarSkillDepotData> avatarSkillDepotDataMap = new Int2ObjectOpenHashMap<>(); private static final Int2ObjectMap<AvatarSkillDepotData> avatarSkillDepotDataMap = new Int2ObjectOpenHashMap<>();
private static final Int2ObjectMap<AvatarSkillData> avatarSkillDataMap = new Int2ObjectOpenHashMap<>(); private static final Int2ObjectMap<AvatarSkillData> avatarSkillDataMap = new Int2ObjectOpenHashMap<>();
private static final Int2ObjectMap<AvatarCurveData> avatarCurveDataMap = new Int2ObjectLinkedOpenHashMap<>(); private static final Int2ObjectMap<AvatarCurveData> avatarCurveDataMap = new Int2ObjectLinkedOpenHashMap<>();
private static final Int2ObjectMap<AvatarFetterLevelData> avatarFetterLevelDataMap = new Int2ObjectOpenHashMap<>();
private static final Int2ObjectMap<AvatarPromoteData> avatarPromoteDataMap = new Int2ObjectOpenHashMap<>(); private static final Int2ObjectMap<AvatarPromoteData> avatarPromoteDataMap = new Int2ObjectOpenHashMap<>();
private static final Int2ObjectMap<AvatarTalentData> avatarTalentDataMap = new Int2ObjectOpenHashMap<>(); private static final Int2ObjectMap<AvatarTalentData> avatarTalentDataMap = new Int2ObjectOpenHashMap<>();
private static final Int2ObjectMap<ProudSkillData> proudSkillDataMap = new Int2ObjectOpenHashMap<>(); private static final Int2ObjectMap<ProudSkillData> proudSkillDataMap = new Int2ObjectOpenHashMap<>();
...@@ -57,6 +58,8 @@ public class GenshinData { ...@@ -57,6 +58,8 @@ public class GenshinData {
private static final Int2ObjectMap<SceneData> sceneDataMap = new Int2ObjectLinkedOpenHashMap<>(); private static final Int2ObjectMap<SceneData> sceneDataMap = new Int2ObjectLinkedOpenHashMap<>();
private static final Int2ObjectMap<FetterData> fetterDataMap = new Int2ObjectOpenHashMap<>(); private static final Int2ObjectMap<FetterData> fetterDataMap = new Int2ObjectOpenHashMap<>();
private static final Int2ObjectMap<FetterCharacterCardData> fetterCharacterCardDataMap = new Int2ObjectOpenHashMap<>();
private static final Int2ObjectMap<RewardData> rewardDataMap = new Int2ObjectOpenHashMap<>();
// Cache // Cache
private static Map<Integer, List<Integer>> fetters = new HashMap<>(); private static Map<Integer, List<Integer>> fetters = new HashMap<>();
...@@ -114,6 +117,14 @@ public class GenshinData { ...@@ -114,6 +117,14 @@ public class GenshinData {
return playerLevelDataMap; return playerLevelDataMap;
} }
public static Int2ObjectMap<AvatarFetterLevelData> getAvatarFetterLevelDataMap() {
return avatarFetterLevelDataMap;
}
public static Int2ObjectMap<FetterCharacterCardData> getFetterCharacterCardDataMap() {
return fetterCharacterCardDataMap;
}
public static Int2ObjectMap<AvatarLevelData> getAvatarLevelDataMap() { public static Int2ObjectMap<AvatarLevelData> getAvatarLevelDataMap() {
return avatarLevelDataMap; return avatarLevelDataMap;
} }
...@@ -175,6 +186,11 @@ public class GenshinData { ...@@ -175,6 +186,11 @@ public class GenshinData {
AvatarLevelData levelData = avatarLevelDataMap.get(level); AvatarLevelData levelData = avatarLevelDataMap.get(level);
return levelData != null ? levelData.getExp() : 0; return levelData != null ? levelData.getExp() : 0;
} }
public static int getAvatarFetterLevelExpRequired(int level) {
AvatarFetterLevelData levelData = avatarFetterLevelDataMap.get(level);
return levelData != null ? levelData.getExp() : 0;
}
public static Int2ObjectMap<ProudSkillData> getProudSkillDataMap() { public static Int2ObjectMap<ProudSkillData> getProudSkillDataMap() {
return proudSkillDataMap; return proudSkillDataMap;
...@@ -228,6 +244,10 @@ public class GenshinData { ...@@ -228,6 +244,10 @@ public class GenshinData {
return sceneDataMap; return sceneDataMap;
} }
public static Int2ObjectMap<RewardData> getRewardDataMap() {
return rewardDataMap;
}
public static Map<Integer, List<Integer>> getFetterDataEntries() { public static Map<Integer, List<Integer>> getFetterDataEntries() {
if (fetters.isEmpty()) { if (fetters.isEmpty()) {
fetterDataMap.forEach((k, v) -> { fetterDataMap.forEach((k, v) -> {
......
package emu.grasscutter.data.common;
import java.util.List;
public class OpenCondData {
private String CondType;
private List<Integer> ParamList;
public String getCondType() {
return CondType;
}
public void setCondType(String condType) {
CondType = condType;
}
public List<Integer> getParamList() {
return ParamList;
}
public void setParamList(List<Integer> paramList) {
ParamList = paramList;
}
}
package emu.grasscutter.data.common;
public class RewardItemData {
private int ItemId;
private int ItemCount;
public int getItemId() {
return ItemId;
}
public void setItemId(int itemId) {
ItemId = itemId;
}
public int getItemCount() {
return ItemCount;
}
public void setItemCount(int itemCount) {
ItemCount = itemCount;
}
}
...@@ -57,6 +57,8 @@ public class AvatarData extends GenshinResource { ...@@ -57,6 +57,8 @@ public class AvatarData extends GenshinResource {
private IntList abilities; private IntList abilities;
private List<Integer> fetters; private List<Integer> fetters;
private int nameCardRewardId;
private int nameCardId;
@Override @Override
public int getId(){ public int getId(){
...@@ -199,12 +201,28 @@ public class AvatarData extends GenshinResource { ...@@ -199,12 +201,28 @@ public class AvatarData extends GenshinResource {
return fetters; return fetters;
} }
public int getNameCardRewardId() {
return nameCardRewardId;
}
public int getNameCardId() {
return nameCardId;
}
@Override @Override
public void onLoad() { public void onLoad() {
this.skillDepot = GenshinData.getAvatarSkillDepotDataMap().get(this.SkillDepotId); this.skillDepot = GenshinData.getAvatarSkillDepotDataMap().get(this.SkillDepotId);
// Get fetters from GenshinData // Get fetters from GenshinData
this.fetters = GenshinData.getFetterDataEntries().get(this.Id); this.fetters = GenshinData.getFetterDataEntries().get(this.Id);
if (GenshinData.getFetterCharacterCardDataMap().get(this.Id) != null) {
this.nameCardRewardId = GenshinData.getFetterCharacterCardDataMap().get(this.Id).getRewardId();
}
if (GenshinData.getRewardDataMap().get(this.nameCardRewardId) != null) {
this.nameCardId = GenshinData.getRewardDataMap().get(this.nameCardRewardId).getRewardItemList().get(0).getItemId();
}
int size = GenshinData.getAvatarCurveDataMap().size(); int size = GenshinData.getAvatarCurveDataMap().size();
this.hpGrowthCurve = new float[size]; this.hpGrowthCurve = new float[size];
......
package emu.grasscutter.data.def;
import emu.grasscutter.data.GenshinResource;
import emu.grasscutter.data.ResourceType;
@ResourceType(name = "AvatarFettersLevelExcelConfigData.json")
public class AvatarFetterLevelData extends GenshinResource {
private int FetterLevel;
private int NeedExp;
@Override
public int getId() {
return this.FetterLevel;
}
public int getLevel() {
return FetterLevel;
}
public int getExp() {
return NeedExp;
}
}
package emu.grasscutter.data.def;
import emu.grasscutter.data.GenshinResource;
import emu.grasscutter.data.ResourceType;
import emu.grasscutter.data.ResourceType.LoadPriority;
@ResourceType(name = "FetterCharacterCardExcelConfigData.json", loadPriority = LoadPriority.HIGHEST)
public class FetterCharacterCardData extends GenshinResource {
private int AvatarId;
private int RewardId;
@Override
public int getId() {
return AvatarId;
}
public int getRewardId() {
return RewardId;
}
@Override
public void onLoad() {
}
}
package emu.grasscutter.data.def; package emu.grasscutter.data.def;
import java.util.List;
import emu.grasscutter.data.GenshinResource; import emu.grasscutter.data.GenshinResource;
import emu.grasscutter.data.ResourceType; import emu.grasscutter.data.ResourceType;
import emu.grasscutter.data.ResourceType.LoadPriority; import emu.grasscutter.data.ResourceType.LoadPriority;
import emu.grasscutter.data.common.OpenCondData;
@ResourceType(name = {"FetterInfoExcelConfigData.json", "FettersExcelConfigData.json", "FetterStoryExcelConfigData.json"}, loadPriority = LoadPriority.HIGHEST) @ResourceType(name = {"FetterInfoExcelConfigData.json", "FettersExcelConfigData.json", "FetterStoryExcelConfigData.json"}, loadPriority = LoadPriority.HIGHEST)
public class FetterData extends GenshinResource { public class FetterData extends GenshinResource {
private int AvatarId; private int AvatarId;
private int FetterId; private int FetterId;
private List<OpenCondData> OpenCond;
@Override @Override
public int getId() { public int getId() {
...@@ -18,6 +22,10 @@ public class FetterData extends GenshinResource { ...@@ -18,6 +22,10 @@ public class FetterData extends GenshinResource {
return AvatarId; return AvatarId;
} }
public List<OpenCondData> getOpenConds() {
return OpenCond;
}
@Override @Override
public void onLoad() { public void onLoad() {
} }
......
package emu.grasscutter.data.def;
import java.util.List;
import emu.grasscutter.data.GenshinResource;
import emu.grasscutter.data.ResourceType;
import emu.grasscutter.data.common.RewardItemData;
@ResourceType(name = "RewardExcelConfigData.json")
public class RewardData extends GenshinResource {
public int RewardId;
public List<RewardItemData> RewardItemList;
@Override
public int getId() {
return RewardId;
}
public List<RewardItemData> getRewardItemList() {
return RewardItemList;
}
@Override
public void onLoad() {
}
}
...@@ -101,7 +101,7 @@ public final class DatabaseManager { ...@@ -101,7 +101,7 @@ public final class DatabaseManager {
} }
public static synchronized int getNextId(Class<?> c) { public static synchronized int getNextId(Class<?> c) {
DatabaseCounter counter = getDatastore().find(DatabaseCounter.class).filter(Filters.eq("_id", c.getName())).first(); DatabaseCounter counter = getDatastore().find(DatabaseCounter.class).filter(Filters.eq("_id", c.getSimpleName())).first();
if (counter == null) { if (counter == null) {
counter = new DatabaseCounter(c.getSimpleName()); counter = new DatabaseCounter(c.getSimpleName());
} }
......
package emu.grasscutter.game; package emu.grasscutter.game;
import java.util.*;
import dev.morphia.annotations.*; import dev.morphia.annotations.*;
import emu.grasscutter.GenshinConstants; import emu.grasscutter.GenshinConstants;
import emu.grasscutter.Grasscutter; import emu.grasscutter.Grasscutter;
...@@ -23,7 +21,6 @@ import emu.grasscutter.game.props.ActionReason; ...@@ -23,7 +21,6 @@ import emu.grasscutter.game.props.ActionReason;
import emu.grasscutter.game.props.PlayerProperty; import emu.grasscutter.game.props.PlayerProperty;
import emu.grasscutter.net.packet.GenshinPacket; import emu.grasscutter.net.packet.GenshinPacket;
import emu.grasscutter.net.proto.AbilityInvokeEntryOuterClass.AbilityInvokeEntry; import emu.grasscutter.net.proto.AbilityInvokeEntryOuterClass.AbilityInvokeEntry;
import emu.grasscutter.net.proto.BirthdayOuterClass.Birthday;
import emu.grasscutter.net.proto.CombatInvokeEntryOuterClass.CombatInvokeEntry; import emu.grasscutter.net.proto.CombatInvokeEntryOuterClass.CombatInvokeEntry;
import emu.grasscutter.net.proto.HeadImageOuterClass.HeadImage; import emu.grasscutter.net.proto.HeadImageOuterClass.HeadImage;
import emu.grasscutter.net.proto.InteractTypeOuterClass.InteractType; import emu.grasscutter.net.proto.InteractTypeOuterClass.InteractType;
...@@ -35,38 +32,18 @@ import emu.grasscutter.net.proto.SocialDetailOuterClass.SocialDetail; ...@@ -35,38 +32,18 @@ import emu.grasscutter.net.proto.SocialDetailOuterClass.SocialDetail;
import emu.grasscutter.net.proto.WorldPlayerLocationInfoOuterClass.WorldPlayerLocationInfo; import emu.grasscutter.net.proto.WorldPlayerLocationInfoOuterClass.WorldPlayerLocationInfo;
import emu.grasscutter.server.game.GameServer; import emu.grasscutter.server.game.GameServer;
import emu.grasscutter.server.game.GameSession; import emu.grasscutter.server.game.GameSession;
import emu.grasscutter.server.packet.send.PacketAbilityInvocationsNotify; import emu.grasscutter.server.packet.send.*;
import emu.grasscutter.server.packet.send.PacketAvatarAddNotify;
import emu.grasscutter.server.packet.send.PacketAvatarDataNotify;
import emu.grasscutter.server.packet.send.PacketAvatarGainCostumeNotify;
import emu.grasscutter.server.packet.send.PacketAvatarGainFlycloakNotify;
import emu.grasscutter.server.packet.send.PacketClientAbilityInitFinishNotify;
import emu.grasscutter.server.packet.send.PacketCombatInvocationsNotify;
import emu.grasscutter.server.packet.send.PacketGadgetInteractRsp;
import emu.grasscutter.server.packet.send.PacketItemAddHintNotify;
import emu.grasscutter.server.packet.send.PacketOpenStateUpdateNotify;
import emu.grasscutter.server.packet.send.PacketPlayerApplyEnterMpResultNotify;
import emu.grasscutter.server.packet.send.PacketPlayerDataNotify;
import emu.grasscutter.server.packet.send.PacketPlayerEnterSceneNotify;
import emu.grasscutter.server.packet.send.PacketPlayerPropNotify;
import emu.grasscutter.server.packet.send.PacketPlayerStoreNotify;
import emu.grasscutter.server.packet.send.PacketPrivateChatNotify;
import emu.grasscutter.server.packet.send.PacketScenePlayerLocationNotify;
import emu.grasscutter.server.packet.send.PacketSetNameCardRsp;
import emu.grasscutter.server.packet.send.PacketStoreWeightLimitNotify;
import emu.grasscutter.server.packet.send.PacketUnlockNameCardNotify;
import emu.grasscutter.server.packet.send.PacketWorldPlayerLocationNotify;
import emu.grasscutter.server.packet.send.PacketWorldPlayerRTTNotify;
import emu.grasscutter.utils.Position; import emu.grasscutter.utils.Position;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap; import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import java.util.*;
@Entity(value = "players", useDiscriminator = false) @Entity(value = "players", useDiscriminator = false)
public class GenshinPlayer { public class GenshinPlayer {
@Id private int id; @Id private int id;
@Indexed(options = @IndexOptions(unique = true)) private String accountId; @Indexed(options = @IndexOptions(unique = true)) private String accountId;
@Transient private Account account; @Transient private Account account;
private String nickname; private String nickname;
private String signature; private String signature;
...@@ -75,12 +52,12 @@ public class GenshinPlayer { ...@@ -75,12 +52,12 @@ public class GenshinPlayer {
private Position pos; private Position pos;
private Position rotation; private Position rotation;
private PlayerBirthday birthday; private PlayerBirthday birthday;
private Map<Integer, Integer> properties; private Map<Integer, Integer> properties;
private Set<Integer> nameCardList; private Set<Integer> nameCardList;
private Set<Integer> flyCloakList; private Set<Integer> flyCloakList;
private Set<Integer> costumeList; private Set<Integer> costumeList;
@Transient private long nextGuid = 0; @Transient private long nextGuid = 0;
@Transient private int peerId; @Transient private int peerId;
@Transient private World world; @Transient private World world;
...@@ -89,32 +66,34 @@ public class GenshinPlayer { ...@@ -89,32 +66,34 @@ public class GenshinPlayer {
@Transient private AvatarStorage avatars; @Transient private AvatarStorage avatars;
@Transient private Inventory inventory; @Transient private Inventory inventory;
@Transient private FriendsList friendsList; @Transient private FriendsList friendsList;
private TeamManager teamManager; private TeamManager teamManager;
private PlayerGachaInfo gachaInfo; private PlayerGachaInfo gachaInfo;
private PlayerProfile playerProfile; private PlayerProfile playerProfile;
private MpSettingType mpSetting = MpSettingType.MpSettingEnterAfterApply; private MpSettingType mpSetting = MpSettingType.MpSettingEnterAfterApply;
private boolean showAvatar; private boolean showAvatar;
private ArrayList<AvatarProfileData> shownAvatars; private ArrayList<AvatarProfileData> shownAvatars;
private Set<Integer> rewardedLevels;
private int sceneId; private int sceneId;
private int regionId; private int regionId;
private int mainCharacterId; private int mainCharacterId;
private boolean godmode; private boolean godmode;
@Transient private boolean paused; @Transient private boolean paused;
@Transient private int enterSceneToken; @Transient private int enterSceneToken;
@Transient private SceneLoadState sceneState; @Transient private SceneLoadState sceneState;
@Transient private boolean hasSentAvatarDataNotify; @Transient private boolean hasSentAvatarDataNotify;
@Transient private long nextSendPlayerLocTime = 0; @Transient private long nextSendPlayerLocTime = 0;
@Transient private final Int2ObjectMap<CoopRequest> coopRequests; @Transient private final Int2ObjectMap<CoopRequest> coopRequests;
@Transient private final InvokeHandler<CombatInvokeEntry> combatInvokeHandler; @Transient private final InvokeHandler<CombatInvokeEntry> combatInvokeHandler;
@Transient private final InvokeHandler<AbilityInvokeEntry> abilityInvokeHandler; @Transient private final InvokeHandler<AbilityInvokeEntry> abilityInvokeHandler;
@Transient private final InvokeHandler<AbilityInvokeEntry> clientAbilityInitFinishHandler; @Transient private final InvokeHandler<AbilityInvokeEntry> clientAbilityInitFinishHandler;
@Deprecated @SuppressWarnings({ "rawtypes", "unchecked" }) // Morphia only! @Deprecated
public GenshinPlayer() { @SuppressWarnings({"rawtypes", "unchecked"}) // Morphia only!
public GenshinPlayer() {
this.inventory = new Inventory(this); this.inventory = new Inventory(this);
this.avatars = new AvatarStorage(this); this.avatars = new AvatarStorage(this);
this.friendsList = new FriendsList(this); this.friendsList = new FriendsList(this);
...@@ -127,24 +106,25 @@ public class GenshinPlayer { ...@@ -127,24 +106,25 @@ public class GenshinPlayer {
} }
this.properties.put(prop.getId(), 0); this.properties.put(prop.getId(), 0);
} }
this.gachaInfo = new PlayerGachaInfo(); this.gachaInfo = new PlayerGachaInfo();
this.nameCardList = new HashSet<>(); this.nameCardList = new HashSet<>();
this.flyCloakList = new HashSet<>(); this.flyCloakList = new HashSet<>();
this.costumeList = new HashSet<>(); this.costumeList = new HashSet<>();
this.setSceneId(3); this.setSceneId(3);
this.setRegionId(1); this.setRegionId(1);
this.sceneState = SceneLoadState.NONE; this.sceneState = SceneLoadState.NONE;
this.coopRequests = new Int2ObjectOpenHashMap<>(); this.coopRequests = new Int2ObjectOpenHashMap<>();
this.combatInvokeHandler = new InvokeHandler(PacketCombatInvocationsNotify.class); this.combatInvokeHandler = new InvokeHandler(PacketCombatInvocationsNotify.class);
this.abilityInvokeHandler = new InvokeHandler(PacketAbilityInvocationsNotify.class); this.abilityInvokeHandler = new InvokeHandler(PacketAbilityInvocationsNotify.class);
this.clientAbilityInitFinishHandler = new InvokeHandler(PacketClientAbilityInitFinishNotify.class); this.clientAbilityInitFinishHandler = new InvokeHandler(PacketClientAbilityInitFinishNotify.class);
this.birthday = new PlayerBirthday(); this.birthday = new PlayerBirthday();
this.rewardedLevels = new HashSet<>();
} }
// On player creation // On player creation
public GenshinPlayer(GameSession session) { public GenshinPlayer(GameSession session) {
this(); this();
...@@ -176,7 +156,7 @@ public class GenshinPlayer { ...@@ -176,7 +156,7 @@ public class GenshinPlayer {
public void setUid(int id) { public void setUid(int id) {
this.id = id; this.id = id;
} }
public long getNextGenshinGuid() { public long getNextGenshinGuid() {
long nextId = ++this.nextGuid; long nextId = ++this.nextGuid;
return ((long) this.getUid() << 32) + nextId; return ((long) this.getUid() << 32) + nextId;
...@@ -198,23 +178,23 @@ public class GenshinPlayer { ...@@ -198,23 +178,23 @@ public class GenshinPlayer {
public void setSession(GameSession session) { public void setSession(GameSession session) {
this.session = session; this.session = session;
} }
public boolean isOnline() { public boolean isOnline() {
return this.getSession() != null && this.getSession().isActive(); return this.getSession() != null && this.getSession().isActive();
} }
public GameServer getServer() { public GameServer getServer() {
return this.getSession().getServer(); return this.getSession().getServer();
} }
public synchronized World getWorld() { public synchronized World getWorld() {
return this.world; return this.world;
} }
public synchronized void setWorld(World world) { public synchronized void setWorld(World world) {
this.world = world; this.world = world;
} }
public GenshinScene getScene() { public GenshinScene getScene() {
return scene; return scene;
} }
...@@ -235,7 +215,7 @@ public class GenshinPlayer { ...@@ -235,7 +215,7 @@ public class GenshinPlayer {
this.nickname = nickName; this.nickname = nickName;
this.updateProfile(); this.updateProfile();
} }
public int getHeadImage() { public int getHeadImage() {
return headImage; return headImage;
} }
...@@ -257,71 +237,71 @@ public class GenshinPlayer { ...@@ -257,71 +237,71 @@ public class GenshinPlayer {
public Position getPos() { public Position getPos() {
return pos; return pos;
} }
public Position getRotation() { public Position getRotation() {
return rotation; return rotation;
} }
public int getLevel() { public int getLevel() {
return this.getProperty(PlayerProperty.PROP_PLAYER_LEVEL); return this.getProperty(PlayerProperty.PROP_PLAYER_LEVEL);
} }
public int getExp() { public int getExp() {
return this.getProperty(PlayerProperty.PROP_PLAYER_EXP); return this.getProperty(PlayerProperty.PROP_PLAYER_EXP);
} }
public int getWorldLevel() { public int getWorldLevel() {
return this.getProperty(PlayerProperty.PROP_PLAYER_WORLD_LEVEL); return this.getProperty(PlayerProperty.PROP_PLAYER_WORLD_LEVEL);
} }
public int getPrimogems() { public int getPrimogems() {
return this.getProperty(PlayerProperty.PROP_PLAYER_HCOIN); return this.getProperty(PlayerProperty.PROP_PLAYER_HCOIN);
} }
public void setPrimogems(int primogem) { public void setPrimogems(int primogem) {
this.setProperty(PlayerProperty.PROP_PLAYER_HCOIN, primogem); this.setProperty(PlayerProperty.PROP_PLAYER_HCOIN, primogem);
this.sendPacket(new PacketPlayerPropNotify(this, PlayerProperty.PROP_PLAYER_HCOIN)); this.sendPacket(new PacketPlayerPropNotify(this, PlayerProperty.PROP_PLAYER_HCOIN));
} }
public int getMora() { public int getMora() {
return this.getProperty(PlayerProperty.PROP_PLAYER_SCOIN); return this.getProperty(PlayerProperty.PROP_PLAYER_SCOIN);
} }
public void setMora(int mora) { public void setMora(int mora) {
this.setProperty(PlayerProperty.PROP_PLAYER_SCOIN, mora); this.setProperty(PlayerProperty.PROP_PLAYER_SCOIN, mora);
this.sendPacket(new PacketPlayerPropNotify(this, PlayerProperty.PROP_PLAYER_SCOIN)); this.sendPacket(new PacketPlayerPropNotify(this, PlayerProperty.PROP_PLAYER_SCOIN));
} }
private int getExpRequired(int level) { private int getExpRequired(int level) {
PlayerLevelData levelData = GenshinData.getPlayerLevelDataMap().get(level); PlayerLevelData levelData = GenshinData.getPlayerLevelDataMap().get(level);
return levelData != null ? levelData.getExp() : 0; return levelData != null ? levelData.getExp() : 0;
} }
private float getExpModifier() { private float getExpModifier() {
return Grasscutter.getConfig().getGameServerOptions().getGameRates().ADVENTURE_EXP_RATE; return Grasscutter.getConfig().getGameServerOptions().getGameRates().ADVENTURE_EXP_RATE;
} }
// Affected by exp rate // Affected by exp rate
public void earnExp(int exp) { public void earnExp(int exp) {
addExpDirectly((int) (exp * getExpModifier())); addExpDirectly((int) (exp * getExpModifier()));
} }
// Directly give player exp // Directly give player exp
public void addExpDirectly(int gain) { public void addExpDirectly(int gain) {
boolean hasLeveledUp = false; boolean hasLeveledUp = false;
int level = getLevel(); int level = getLevel();
int exp = getExp(); int exp = getExp();
int reqExp = getExpRequired(level); int reqExp = getExpRequired(level);
exp += gain; exp += gain;
while (exp >= reqExp && reqExp > 0) { while (exp >= reqExp && reqExp > 0) {
exp -= reqExp; exp -= reqExp;
level += 1; level += 1;
reqExp = getExpRequired(level); reqExp = getExpRequired(level);
hasLeveledUp = true; hasLeveledUp = true;
} }
if (hasLeveledUp) { if (hasLeveledUp) {
// Set level property // Set level property
this.setProperty(PlayerProperty.PROP_PLAYER_LEVEL, level); this.setProperty(PlayerProperty.PROP_PLAYER_LEVEL, level);
...@@ -330,18 +310,18 @@ public class GenshinPlayer { ...@@ -330,18 +310,18 @@ public class GenshinPlayer {
// Update player with packet // Update player with packet
this.sendPacket(new PacketPlayerPropNotify(this, PlayerProperty.PROP_PLAYER_LEVEL)); this.sendPacket(new PacketPlayerPropNotify(this, PlayerProperty.PROP_PLAYER_LEVEL));
} }
// Set exp // Set exp
this.setProperty(PlayerProperty.PROP_PLAYER_EXP, exp); this.setProperty(PlayerProperty.PROP_PLAYER_EXP, exp);
// Update player with packet // Update player with packet
this.sendPacket(new PacketPlayerPropNotify(this, PlayerProperty.PROP_PLAYER_EXP)); this.sendPacket(new PacketPlayerPropNotify(this, PlayerProperty.PROP_PLAYER_EXP));
} }
private void updateProfile() { private void updateProfile() {
getProfile().syncWithCharacter(this); getProfile().syncWithCharacter(this);
} }
public boolean isFirstLoginEnterScene() { public boolean isFirstLoginEnterScene() {
return !this.hasSentAvatarDataNotify; return !this.hasSentAvatarDataNotify;
} }
...@@ -364,11 +344,11 @@ public class GenshinPlayer { ...@@ -364,11 +344,11 @@ public class GenshinPlayer {
public Map<Integer, Integer> getProperties() { public Map<Integer, Integer> getProperties() {
return properties; return properties;
} }
public void setProperty(PlayerProperty prop, int value) { public void setProperty(PlayerProperty prop, int value) {
getProperties().put(prop.getId(), value); getProperties().put(prop.getId(), value);
} }
public int getProperty(PlayerProperty prop) { public int getProperty(PlayerProperty prop) {
return getProperties().get(prop.getId()); return getProperties().get(prop.getId());
} }
...@@ -376,11 +356,11 @@ public class GenshinPlayer { ...@@ -376,11 +356,11 @@ public class GenshinPlayer {
public Set<Integer> getFlyCloakList() { public Set<Integer> getFlyCloakList() {
return flyCloakList; return flyCloakList;
} }
public Set<Integer> getCostumeList() { public Set<Integer> getCostumeList() {
return costumeList; return costumeList;
} }
public Set<Integer> getNameCardList() { public Set<Integer> getNameCardList() {
return this.nameCardList; return this.nameCardList;
} }
...@@ -388,7 +368,7 @@ public class GenshinPlayer { ...@@ -388,7 +368,7 @@ public class GenshinPlayer {
public MpSettingType getMpSetting() { public MpSettingType getMpSetting() {
return mpSetting; return mpSetting;
} }
public synchronized Int2ObjectMap<CoopRequest> getCoopRequests() { public synchronized Int2ObjectMap<CoopRequest> getCoopRequests() {
return coopRequests; return coopRequests;
} }
...@@ -396,7 +376,7 @@ public class GenshinPlayer { ...@@ -396,7 +376,7 @@ public class GenshinPlayer {
public InvokeHandler<CombatInvokeEntry> getCombatInvokeHandler() { public InvokeHandler<CombatInvokeEntry> getCombatInvokeHandler() {
return this.combatInvokeHandler; return this.combatInvokeHandler;
} }
public InvokeHandler<AbilityInvokeEntry> getAbilityInvokeHandler() { public InvokeHandler<AbilityInvokeEntry> getAbilityInvokeHandler() {
return this.abilityInvokeHandler; return this.abilityInvokeHandler;
} }
...@@ -412,11 +392,11 @@ public class GenshinPlayer { ...@@ -412,11 +392,11 @@ public class GenshinPlayer {
public AvatarStorage getAvatars() { public AvatarStorage getAvatars() {
return avatars; return avatars;
} }
public Inventory getInventory() { public Inventory getInventory() {
return inventory; return inventory;
} }
public FriendsList getFriendsList() { public FriendsList getFriendsList() {
return this.friendsList; return this.friendsList;
} }
...@@ -469,7 +449,7 @@ public class GenshinPlayer { ...@@ -469,7 +449,7 @@ public class GenshinPlayer {
public void setPaused(boolean newPauseState) { public void setPaused(boolean newPauseState) {
boolean oldPauseState = this.paused; boolean oldPauseState = this.paused;
this.paused = newPauseState; this.paused = newPauseState;
if (newPauseState && !oldPauseState) { if (newPauseState && !oldPauseState) {
this.onPause(); this.onPause();
} else if (oldPauseState && !newPauseState) { } else if (oldPauseState && !newPauseState) {
...@@ -484,7 +464,7 @@ public class GenshinPlayer { ...@@ -484,7 +464,7 @@ public class GenshinPlayer {
public void setSceneLoadState(SceneLoadState sceneState) { public void setSceneLoadState(SceneLoadState sceneState) {
this.sceneState = sceneState; this.sceneState = sceneState;
} }
public boolean isInMultiplayer() { public boolean isInMultiplayer() {
return this.getWorld() != null && this.getWorld().isMultiplayer(); return this.getWorld() != null && this.getWorld().isMultiplayer();
} }
...@@ -504,7 +484,7 @@ public class GenshinPlayer { ...@@ -504,7 +484,7 @@ public class GenshinPlayer {
public void setRegionId(int regionId) { public void setRegionId(int regionId) {
this.regionId = regionId; this.regionId = regionId;
} }
public boolean inGodmode() { public boolean inGodmode() {
return godmode; return godmode;
} }
...@@ -523,11 +503,11 @@ public class GenshinPlayer { ...@@ -523,11 +503,11 @@ public class GenshinPlayer {
public void addAvatar(GenshinAvatar avatar) { public void addAvatar(GenshinAvatar avatar) {
boolean result = getAvatars().addAvatar(avatar); boolean result = getAvatars().addAvatar(avatar);
if (result) { if (result) {
// Add starting weapon // Add starting weapon
getAvatars().addStartingWeapon(avatar); getAvatars().addStartingWeapon(avatar);
// Try adding to team if possible // Try adding to team if possible
//List<EntityAvatar> currentTeam = this.getTeamManager().getCurrentTeam(); //List<EntityAvatar> currentTeam = this.getTeamManager().getCurrentTeam();
boolean addedToTeam = false; boolean addedToTeam = false;
...@@ -537,7 +517,7 @@ public class GenshinPlayer { ...@@ -537,7 +517,7 @@ public class GenshinPlayer {
addedToTeam = currentTeam addedToTeam = currentTeam
} }
*/ */
// Done // Done
if (hasSentAvatarDataNotify()) { if (hasSentAvatarDataNotify()) {
// Recalc stats // Recalc stats
...@@ -549,55 +529,56 @@ public class GenshinPlayer { ...@@ -549,55 +529,56 @@ public class GenshinPlayer {
// Failed adding avatar // Failed adding avatar
} }
} }
public void addFlycloak(int flycloakId) { public void addFlycloak(int flycloakId) {
this.getFlyCloakList().add(flycloakId); this.getFlyCloakList().add(flycloakId);
this.sendPacket(new PacketAvatarGainFlycloakNotify(flycloakId)); this.sendPacket(new PacketAvatarGainFlycloakNotify(flycloakId));
} }
public void addCostume(int costumeId) { public void addCostume(int costumeId) {
this.getCostumeList().add(costumeId); this.getCostumeList().add(costumeId);
this.sendPacket(new PacketAvatarGainCostumeNotify(costumeId)); this.sendPacket(new PacketAvatarGainCostumeNotify(costumeId));
} }
public void addNameCard(int nameCardId) { public void addNameCard(int nameCardId) {
this.getNameCardList().add(nameCardId); this.getNameCardList().add(nameCardId);
this.sendPacket(new PacketUnlockNameCardNotify(nameCardId)); this.sendPacket(new PacketUnlockNameCardNotify(nameCardId));
} }
public void setNameCard(int nameCardId) { public void setNameCard(int nameCardId) {
if (!this.getNameCardList().contains(nameCardId)) { if (!this.getNameCardList().contains(nameCardId)) {
return; return;
} }
this.setNameCardId(nameCardId); this.setNameCardId(nameCardId);
this.sendPacket(new PacketSetNameCardRsp(nameCardId)); this.sendPacket(new PacketSetNameCardRsp(nameCardId));
} }
public void dropMessage(Object message) { public void dropMessage(Object message) {
this.sendPacket(new PacketPrivateChatNotify(GenshinConstants.SERVER_CONSOLE_UID, getUid(), message.toString())); this.sendPacket(new PacketPrivateChatNotify(GenshinConstants.SERVER_CONSOLE_UID, getUid(), message.toString()));
} }
/** /**
* Sends a message to another player. * Sends a message to another player.
* @param sender The sender of the message. *
* @param sender The sender of the message.
* @param message The message to send. * @param message The message to send.
*/ */
public void sendMessage(GenshinPlayer sender, Object message) { public void sendMessage(GenshinPlayer sender, Object message) {
this.sendPacket(new PacketPrivateChatNotify(sender.getUid(), this.getUid(), message.toString())); this.sendPacket(new PacketPrivateChatNotify(sender.getUid(), this.getUid(), message.toString()));
} }
public void interactWith(int gadgetEntityId) { public void interactWith(int gadgetEntityId) {
GenshinEntity entity = getScene().getEntityById(gadgetEntityId); GenshinEntity entity = getScene().getEntityById(gadgetEntityId);
if (entity == null) { if (entity == null) {
return; return;
} }
// Delete // Delete
entity.getScene().removeEntity(entity); entity.getScene().removeEntity(entity);
// Handle // Handle
if (entity instanceof EntityItem) { if (entity instanceof EntityItem) {
// Pick item // Pick item
...@@ -610,24 +591,24 @@ public class GenshinPlayer { ...@@ -610,24 +591,24 @@ public class GenshinPlayer {
this.sendPacket(new PacketItemAddHintNotify(item, ActionReason.SubfieldDrop)); this.sendPacket(new PacketItemAddHintNotify(item, ActionReason.SubfieldDrop));
} }
} }
return; return;
} }
public void onPause() { public void onPause() {
} }
public void onUnpause() { public void onUnpause() {
} }
public void sendPacket(GenshinPacket packet) { public void sendPacket(GenshinPacket packet) {
if (this.hasSentAvatarDataNotify) { if (this.hasSentAvatarDataNotify) {
this.getSession().send(packet); this.getSession().send(packet);
} }
} }
public OnlinePlayerInfo getOnlinePlayerInfo() { public OnlinePlayerInfo getOnlinePlayerInfo() {
OnlinePlayerInfo.Builder onlineInfo = OnlinePlayerInfo.newBuilder() OnlinePlayerInfo.Builder onlineInfo = OnlinePlayerInfo.newBuilder()
.setUid(this.getUid()) .setUid(this.getUid())
...@@ -637,17 +618,17 @@ public class GenshinPlayer { ...@@ -637,17 +618,17 @@ public class GenshinPlayer {
.setNameCardId(this.getNameCardId()) .setNameCardId(this.getNameCardId())
.setSignature(this.getSignature()) .setSignature(this.getSignature())
.setAvatar(HeadImage.newBuilder().setAvatarId(this.getHeadImage())); .setAvatar(HeadImage.newBuilder().setAvatarId(this.getHeadImage()));
if (this.getWorld() != null) { if (this.getWorld() != null) {
onlineInfo.setCurPlayerNumInWorld(this.getWorld().getPlayers().indexOf(this) + 1); onlineInfo.setCurPlayerNumInWorld(this.getWorld().getPlayers().indexOf(this) + 1);
} else { } else {
onlineInfo.setCurPlayerNumInWorld(1); onlineInfo.setCurPlayerNumInWorld(1);
} }
return onlineInfo.build(); return onlineInfo.build();
} }
public PlayerBirthday getBirthday(){ public PlayerBirthday getBirthday() {
return this.birthday; return this.birthday;
} }
...@@ -656,6 +637,18 @@ public class GenshinPlayer { ...@@ -656,6 +637,18 @@ public class GenshinPlayer {
this.updateProfile(); this.updateProfile();
} }
public boolean hasBirthday() {
return this.birthday.getDay() > 0;
}
public Set<Integer> getRewardedLevels() {
return rewardedLevels;
}
public void setRewardedLevels(Set<Integer> rewardedLevels) {
this.rewardedLevels = rewardedLevels;
}
public SocialDetail.Builder getSocialDetail() { public SocialDetail.Builder getSocialDetail() {
SocialDetail.Builder social = SocialDetail.newBuilder() SocialDetail.Builder social = SocialDetail.newBuilder()
.setUid(this.getUid()) .setUid(this.getUid())
...@@ -671,22 +664,22 @@ public class GenshinPlayer { ...@@ -671,22 +664,22 @@ public class GenshinPlayer {
.setFinishAchievementNum(0); .setFinishAchievementNum(0);
return social; return social;
} }
public WorldPlayerLocationInfo getWorldPlayerLocationInfo() { public WorldPlayerLocationInfo getWorldPlayerLocationInfo() {
return WorldPlayerLocationInfo.newBuilder() return WorldPlayerLocationInfo.newBuilder()
.setSceneId(this.getSceneId()) .setSceneId(this.getSceneId())
.setPlayerLoc(this.getPlayerLocationInfo()) .setPlayerLoc(this.getPlayerLocationInfo())
.build(); .build();
} }
public PlayerLocationInfo getPlayerLocationInfo() { public PlayerLocationInfo getPlayerLocationInfo() {
return PlayerLocationInfo.newBuilder() return PlayerLocationInfo.newBuilder()
.setUid(this.getUid()) .setUid(this.getUid())
.setPos(this.getPos().toProto()) .setPos(this.getPos().toProto())
.setRot(this.getRotation().toProto()) .setRot(this.getRotation().toProto())
.build(); .build();
} }
public synchronized void onTick() { public synchronized void onTick() {
// Check ping // Check ping
if (this.getLastPingTime() > System.currentTimeMillis() + 60000) { if (this.getLastPingTime() > System.currentTimeMillis() + 60000) {
...@@ -706,7 +699,7 @@ public class GenshinPlayer { ...@@ -706,7 +699,7 @@ public class GenshinPlayer {
if (this.getWorld() != null) { if (this.getWorld() != null) {
// RTT notify - very important to send this often // RTT notify - very important to send this often
this.sendPacket(new PacketWorldPlayerRTTNotify(this.getWorld())); this.sendPacket(new PacketWorldPlayerRTTNotify(this.getWorld()));
// Update player locations if in multiplayer every 5 seconds // Update player locations if in multiplayer every 5 seconds
long time = System.currentTimeMillis(); long time = System.currentTimeMillis();
if (this.getWorld().isMultiplayer() && this.getScene() != null && time > nextSendPlayerLocTime) { if (this.getWorld().isMultiplayer() && this.getScene() != null && time > nextSendPlayerLocTime) {
...@@ -716,7 +709,7 @@ public class GenshinPlayer { ...@@ -716,7 +709,7 @@ public class GenshinPlayer {
} }
} }
} }
public void resetSendPlayerLocTime() { public void resetSendPlayerLocTime() {
this.nextSendPlayerLocTime = System.currentTimeMillis() + 5000; this.nextSendPlayerLocTime = System.currentTimeMillis() + 5000;
} }
...@@ -725,7 +718,7 @@ public class GenshinPlayer { ...@@ -725,7 +718,7 @@ public class GenshinPlayer {
private void onLoad() { private void onLoad() {
this.getTeamManager().setPlayer(this); this.getTeamManager().setPlayer(this);
} }
public void save() { public void save() {
DatabaseHelper.savePlayer(this); DatabaseHelper.savePlayer(this);
} }
...@@ -734,78 +727,80 @@ public class GenshinPlayer { ...@@ -734,78 +727,80 @@ public class GenshinPlayer {
// Make sure these exist // Make sure these exist
if (this.getTeamManager() == null) { if (this.getTeamManager() == null) {
this.teamManager = new TeamManager(this); this.teamManager = new TeamManager(this);
} if (this.getProfile().getUid() == 0) { }
if (this.getProfile().getUid() == 0) {
this.getProfile().syncWithCharacter(this); this.getProfile().syncWithCharacter(this);
} }
// Check if player object exists in server // Check if player object exists in server
// TODO - optimize // TODO - optimize
GenshinPlayer exists = this.getServer().getPlayerByUid(getUid()); GenshinPlayer exists = this.getServer().getPlayerByUid(getUid());
if (exists != null) { if (exists != null) {
exists.getSession().close(); exists.getSession().close();
} }
// Load from db // Load from db
this.getAvatars().loadFromDatabase(); this.getAvatars().loadFromDatabase();
this.getInventory().loadFromDatabase(); this.getInventory().loadFromDatabase();
this.getAvatars().postLoad(); this.getAvatars().postLoad();
this.getFriendsList().loadFromDatabase(); this.getFriendsList().loadFromDatabase();
// Create world // Create world
World world = new World(this); World world = new World(this);
world.addPlayer(this); world.addPlayer(this);
// Add to gameserver // Add to gameserver
if (getSession().isActive()) { if (getSession().isActive()) {
getServer().registerPlayer(this); getServer().registerPlayer(this);
getProfile().setPlayer(this); // Set online getProfile().setPlayer(this); // Set online
} }
// Multiplayer setting // Multiplayer setting
this.setProperty(PlayerProperty.PROP_PLAYER_MP_SETTING_TYPE, this.getMpSetting().getNumber()); this.setProperty(PlayerProperty.PROP_PLAYER_MP_SETTING_TYPE, this.getMpSetting().getNumber());
this.setProperty(PlayerProperty.PROP_IS_MP_MODE_AVAILABLE, 1); this.setProperty(PlayerProperty.PROP_IS_MP_MODE_AVAILABLE, 1);
// Packets // Packets
session.send(new PacketPlayerDataNotify(this)); // Player data session.send(new PacketPlayerDataNotify(this)); // Player data
session.send(new PacketStoreWeightLimitNotify()); session.send(new PacketStoreWeightLimitNotify());
session.send(new PacketPlayerStoreNotify(this)); session.send(new PacketPlayerStoreNotify(this));
session.send(new PacketAvatarDataNotify(this)); session.send(new PacketAvatarDataNotify(this));
session.send(new PacketPlayerEnterSceneNotify(this)); // Enter game world session.send(new PacketPlayerEnterSceneNotify(this)); // Enter game world
session.send(new PacketPlayerLevelRewardUpdateNotify(rewardedLevels));
session.send(new PacketOpenStateUpdateNotify()); session.send(new PacketOpenStateUpdateNotify());
// First notify packets sent // First notify packets sent
this.setHasSentAvatarDataNotify(true); this.setHasSentAvatarDataNotify(true);
} }
public void onLogout() { public void onLogout() {
// Leave world // Leave world
if (this.getWorld() != null) { if (this.getWorld() != null) {
this.getWorld().removePlayer(this); this.getWorld().removePlayer(this);
} }
// Status stuff // Status stuff
this.getProfile().syncWithCharacter(this); this.getProfile().syncWithCharacter(this);
this.getProfile().setPlayer(null); // Set offline this.getProfile().setPlayer(null); // Set offline
this.getCoopRequests().clear(); this.getCoopRequests().clear();
// Save to db // Save to db
this.save(); this.save();
this.getTeamManager().saveAvatars(); this.getTeamManager().saveAvatars();
this.getFriendsList().save(); this.getFriendsList().save();
} }
public enum SceneLoadState { public enum SceneLoadState {
NONE (0), LOADING (1), INIT (2), LOADED (3); NONE(0), LOADING(1), INIT(2), LOADED(3);
private final int value; private final int value;
private SceneLoadState(int value) { private SceneLoadState(int value) {
this.value = value; this.value = value;
} }
public int getValue() { public int getValue() {
return this.value; return this.value;
} }
......
...@@ -89,6 +89,12 @@ public class GenshinAvatar { ...@@ -89,6 +89,12 @@ public class GenshinAvatar {
private int flyCloak; private int flyCloak;
private int costume; private int costume;
private int bornTime; private int bornTime;
private int fetterLevel = 1;
private int fetterExp;
private int nameCardRewardId;
private int nameCardId;
public GenshinAvatar() { public GenshinAvatar() {
// Morhpia only! // Morhpia only!
...@@ -107,6 +113,8 @@ public class GenshinAvatar { ...@@ -107,6 +113,8 @@ public class GenshinAvatar {
public GenshinAvatar(AvatarData data) { public GenshinAvatar(AvatarData data) {
this(); this();
this.avatarId = data.getId(); this.avatarId = data.getId();
this.nameCardRewardId = data.getNameCardRewardId();
this.nameCardId = data.getNameCardId();
this.data = data; this.data = data;
this.bornTime = (int) (System.currentTimeMillis() / 1000); this.bornTime = (int) (System.currentTimeMillis() / 1000);
this.flyCloak = 140001; this.flyCloak = 140001;
...@@ -169,6 +177,14 @@ public class GenshinAvatar { ...@@ -169,6 +177,14 @@ public class GenshinAvatar {
this.satiation = satiation; this.satiation = satiation;
} }
public int getNameCardRewardId() {
return nameCardRewardId;
}
public void setNameCardRewardId(int nameCardRewardId) {
this.nameCardRewardId = nameCardRewardId;
}
public int getSatiationPenalty() { public int getSatiationPenalty() {
return satiationPenalty; return satiationPenalty;
} }
...@@ -281,6 +297,30 @@ public class GenshinAvatar { ...@@ -281,6 +297,30 @@ public class GenshinAvatar {
return fetters; return fetters;
} }
public int getFetterLevel() {
return fetterLevel;
}
public void setFetterLevel(int fetterLevel) {
this.fetterLevel = fetterLevel;
}
public int getFetterExp() {
return fetterExp;
}
public void setFetterExp(int fetterExp) {
this.fetterExp = fetterExp;
}
public int getNameCardId() {
return nameCardId;
}
public void setNameCardId(int nameCardId) {
this.nameCardId = nameCardId;
}
public float getCurrentHp() { public float getCurrentHp() {
return currentHp; return currentHp;
} }
...@@ -403,6 +443,8 @@ public class GenshinAvatar { ...@@ -403,6 +443,8 @@ public class GenshinAvatar {
// Fetters // Fetters
this.setFetterList(data.getFetters()); this.setFetterList(data.getFetters());
this.setNameCardRewardId(data.getNameCardRewardId());
this.setNameCardId(data.getNameCardId());
// Get hp percent, set to 100% if none // 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); 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);
...@@ -701,9 +743,14 @@ public class GenshinAvatar { ...@@ -701,9 +743,14 @@ public class GenshinAvatar {
} }
public AvatarInfo toProto() { public AvatarInfo toProto() {
int fetterLevel = this.getFetterLevel();
AvatarFetterInfo.Builder avatarFetter = AvatarFetterInfo.newBuilder() AvatarFetterInfo.Builder avatarFetter = AvatarFetterInfo.newBuilder()
.setExpLevel(10) .setExpLevel(fetterLevel);
.setExpNumber(6325); // Highest Level
if (fetterLevel != 10) {
avatarFetter.setExpNumber(this.getFetterExp());
}
if (this.getFetterList() != null) { if (this.getFetterList() != null) {
for (int i = 0; i < this.getFetterList().size(); i++) { for (int i = 0; i < this.getFetterList().size(); i++) {
...@@ -715,6 +762,12 @@ public class GenshinAvatar { ...@@ -715,6 +762,12 @@ public class GenshinAvatar {
} }
} }
int cardId = this.getNameCardId();
if (this.getPlayer().getNameCardList().contains(cardId)) {
avatarFetter.addRewardedFetterLevelList(10);
}
AvatarInfo.Builder avatarInfo = AvatarInfo.newBuilder() AvatarInfo.Builder avatarInfo = AvatarInfo.newBuilder()
.setAvatarId(this.getAvatarId()) .setAvatarId(this.getAvatarId())
.setGuid(this.getGuid()) .setGuid(this.getGuid())
......
...@@ -90,7 +90,7 @@ public class GenshinItem { ...@@ -90,7 +90,7 @@ public class GenshinItem {
// Equip data // Equip data
if (getItemType() == ItemType.ITEM_WEAPON) { if (getItemType() == ItemType.ITEM_WEAPON) {
this.level = 1; this.level = this.count > 1 ? this.count : 1;
this.affixes = new ArrayList<>(2); this.affixes = new ArrayList<>(2);
if (getItemData().getSkillAffix() != null) { if (getItemData().getSkillAffix() != null) {
for (int skillAffix : getItemData().getSkillAffix()) { for (int skillAffix : getItemData().getSkillAffix()) {
......
...@@ -711,6 +711,31 @@ public class InventoryManager { ...@@ -711,6 +711,31 @@ public class InventoryManager {
player.sendPacket(new PacketAvatarUpgradeRsp(avatar, oldLevel, oldPropMap)); player.sendPacket(new PacketAvatarUpgradeRsp(avatar, oldLevel, oldPropMap));
} }
public void upgradeAvatarFetterLevel(GenshinPlayer player, GenshinAvatar avatar, int expGain) {
// May work. Not test.
int maxLevel = 10; // Keep it until I think of a more "elegant" way
int level = avatar.getFetterLevel();
int exp = avatar.getFetterExp();
int reqExp = GenshinData.getAvatarFetterLevelExpRequired(level);
while (expGain > 0 && reqExp > 0 && level < maxLevel) {
int toGain = Math.min(expGain, reqExp - exp);
exp += toGain;
expGain -= toGain;
if (exp >= reqExp) {
exp = 0;
level += 1;
reqExp = GenshinData.getAvatarFetterLevelExpRequired(level);
}
}
avatar.setFetterLevel(level);
avatar.setFetterExp(exp);
avatar.save();
player.sendPacket(new PacketAvatarPropNotify(avatar));
}
public void upgradeAvatarSkill(GenshinPlayer player, long guid, int skillId) { public void upgradeAvatarSkill(GenshinPlayer player, long guid, int skillId) {
// Sanity checks // Sanity checks
GenshinAvatar avatar = player.getAvatars().getAvatarByGuid(guid); GenshinAvatar avatar = player.getAvatars().getAvatarByGuid(guid);
......
package emu.grasscutter.game.player; package emu.grasscutter.game.player;
import dev.morphia.annotations.Entity;
import emu.grasscutter.net.proto.BirthdayOuterClass.Birthday; import emu.grasscutter.net.proto.BirthdayOuterClass.Birthday;
@Entity
public class PlayerBirthday { public class PlayerBirthday {
private int day; private int day;
private int month; private int month;
......
...@@ -64,9 +64,8 @@ public class MihoyoKcpServer extends Thread { ...@@ -64,9 +64,8 @@ public class MihoyoKcpServer extends Thread {
// Wait until the server socket is closed. // Wait until the server socket is closed.
f.channel().closeFuture().sync(); f.channel().closeFuture().sync();
} catch (Exception e) { } catch (Exception exception) {
// TODO Auto-generated catch block Grasscutter.getLogger().error("Unable to start game server.", exception);
e.printStackTrace();
} finally { } finally {
// Close // Close
finish(); finish();
......
...@@ -26,6 +26,7 @@ import emu.grasscutter.utils.Utils; ...@@ -26,6 +26,7 @@ import emu.grasscutter.utils.Utils;
import javax.net.ssl.KeyManagerFactory; import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext; import javax.net.ssl.SSLContext;
import java.io.*; import java.io.*;
import java.net.BindException;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.net.URI; import java.net.URI;
import java.net.URLDecoder; import java.net.URLDecoder;
...@@ -39,47 +40,48 @@ public final class DispatchServer { ...@@ -39,47 +40,48 @@ public final class DispatchServer {
private final InetSocketAddress address; private final InetSocketAddress address;
private final Gson gson; private final Gson gson;
private final String defaultServerName = "os_usa"; private final String defaultServerName = "os_usa";
public String regionListBase64; public String regionListBase64;
public HashMap<String, RegionData> regions; public HashMap<String, RegionData> regions;
public DispatchServer() { public DispatchServer() {
this.regions = new HashMap<String, RegionData>(); this.regions = new HashMap<String, RegionData>();
this.address = new InetSocketAddress(Grasscutter.getConfig().getDispatchOptions().Ip, Grasscutter.getConfig().getDispatchOptions().Port); this.address = new InetSocketAddress(Grasscutter.getConfig().getDispatchOptions().Ip,
Grasscutter.getConfig().getDispatchOptions().Port);
this.gson = new GsonBuilder().create(); this.gson = new GsonBuilder().create();
this.loadQueries(); this.loadQueries();
this.initRegion(); this.initRegion();
} }
public InetSocketAddress getAddress() { public InetSocketAddress getAddress() {
return address; return address;
} }
public Gson getGsonFactory() { public Gson getGsonFactory() {
return gson; return gson;
} }
public QueryCurrRegionHttpRsp getCurrRegion() { public QueryCurrRegionHttpRsp getCurrRegion() {
// Needs to be fixed by having the game servers connect to the dispatch server. // Needs to be fixed by having the game servers connect to the dispatch server.
if(Grasscutter.getConfig().RunMode.equalsIgnoreCase("HYBRID")) { if (Grasscutter.getConfig().RunMode.equalsIgnoreCase("HYBRID")) {
return regions.get(defaultServerName).parsedRegionQuery; return regions.get(defaultServerName).parsedRegionQuery;
} }
Grasscutter.getLogger().warn("[Dispatch] Unsupported run mode for getCurrRegion()"); Grasscutter.getLogger().warn("[Dispatch] Unsupported run mode for getCurrRegion()");
return null; return null;
} }
public void loadQueries() { public void loadQueries() {
File file; File file;
file = new File(Grasscutter.getConfig().DATA_FOLDER + "query_region_list.txt"); file = new File(Grasscutter.getConfig().DATA_FOLDER + "query_region_list.txt");
if (file.exists()) { if (file.exists()) {
query_region_list = new String(FileUtils.read(file)); query_region_list = new String(FileUtils.read(file));
} else { } else {
Grasscutter.getLogger().warn("[Dispatch] 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"); file = new File(Grasscutter.getConfig().DATA_FOLDER + "query_cur_region.txt");
if (file.exists()) { if (file.exists()) {
query_cur_region = new String(FileUtils.read(file)); query_cur_region = new String(FileUtils.read(file));
...@@ -92,40 +94,58 @@ public final class DispatchServer { ...@@ -92,40 +94,58 @@ public final class DispatchServer {
try { try {
byte[] decoded = Base64.getDecoder().decode(query_region_list); byte[] decoded = Base64.getDecoder().decode(query_region_list);
QueryRegionListHttpRsp rl = QueryRegionListHttpRsp.parseFrom(decoded); QueryRegionListHttpRsp rl = QueryRegionListHttpRsp.parseFrom(decoded);
byte[] decoded2 = Base64.getDecoder().decode(query_cur_region); byte[] decoded2 = Base64.getDecoder().decode(query_cur_region);
QueryCurrRegionHttpRsp regionQuery = QueryCurrRegionHttpRsp.parseFrom(decoded2); QueryCurrRegionHttpRsp regionQuery = QueryCurrRegionHttpRsp.parseFrom(decoded2);
List<RegionSimpleInfo> servers = new ArrayList<RegionSimpleInfo>(); List<RegionSimpleInfo> servers = new ArrayList<RegionSimpleInfo>();
List<String> usedNames = new ArrayList<String>(); // List to check for potential naming conflicts 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 if (Grasscutter.getConfig().RunMode.equalsIgnoreCase("HYBRID")) { // Automatically add the game server if in
// hybrid mode
RegionSimpleInfo server = RegionSimpleInfo.newBuilder() RegionSimpleInfo server = RegionSimpleInfo.newBuilder()
.setName("os_usa") .setName("os_usa")
.setTitle(Grasscutter.getConfig().getGameServerOptions().Name) .setTitle(Grasscutter.getConfig().getGameServerOptions().Name)
.setType("DEV_PUBLIC") .setType("DEV_PUBLIC")
.setDispatchUrl("http" + (Grasscutter.getConfig().getDispatchOptions().FrontHTTPS ? "s" : "") + "://" + (Grasscutter.getConfig().getDispatchOptions().PublicIp.isEmpty() ? Grasscutter.getConfig().getDispatchOptions().Ip : Grasscutter.getConfig().getDispatchOptions().PublicIp) + ":" + (Grasscutter.getConfig().getDispatchOptions().PublicPort != 0 ? Grasscutter.getConfig().getDispatchOptions().PublicPort : Grasscutter.getConfig().getDispatchOptions().Port) + "/query_cur_region_" + defaultServerName) .setDispatchUrl(
"http" + (Grasscutter.getConfig().getDispatchOptions().FrontHTTPS ? "s" : "") + "://"
+ (Grasscutter.getConfig().getDispatchOptions().PublicIp.isEmpty()
? Grasscutter.getConfig().getDispatchOptions().Ip
: Grasscutter.getConfig().getDispatchOptions().PublicIp)
+ ":"
+ (Grasscutter.getConfig().getDispatchOptions().PublicPort != 0
? Grasscutter.getConfig().getDispatchOptions().PublicPort
: Grasscutter.getConfig().getDispatchOptions().Port)
+ "/query_cur_region_" + defaultServerName)
.build(); .build();
usedNames.add(defaultServerName); usedNames.add(defaultServerName);
servers.add(server); servers.add(server);
RegionInfo serverRegion = regionQuery.getRegionInfo().toBuilder() RegionInfo serverRegion = regionQuery.getRegionInfo().toBuilder()
.setIp((Grasscutter.getConfig().getGameServerOptions().PublicIp.isEmpty() ? Grasscutter.getConfig().getGameServerOptions().Ip : Grasscutter.getConfig().getGameServerOptions().PublicIp)) .setIp((Grasscutter.getConfig().getGameServerOptions().PublicIp.isEmpty()
.setPort(Grasscutter.getConfig().getGameServerOptions().PublicPort != 0 ? Grasscutter.getConfig().getGameServerOptions().PublicPort : Grasscutter.getConfig().getGameServerOptions().Port) ? Grasscutter.getConfig().getGameServerOptions().Ip
.setSecretKey(ByteString.copyFrom(FileUtils.read(Grasscutter.getConfig().KEY_FOLDER + "dispatchSeed.bin"))) : Grasscutter.getConfig().getGameServerOptions().PublicIp))
.setPort(Grasscutter.getConfig().getGameServerOptions().PublicPort != 0
? Grasscutter.getConfig().getGameServerOptions().PublicPort
: Grasscutter.getConfig().getGameServerOptions().Port)
.setSecretKey(ByteString
.copyFrom(FileUtils.read(Grasscutter.getConfig().KEY_FOLDER + "dispatchSeed.bin")))
.build(); .build();
QueryCurrRegionHttpRsp parsedRegionQuery = regionQuery.toBuilder().setRegionInfo(serverRegion).build(); QueryCurrRegionHttpRsp parsedRegionQuery = regionQuery.toBuilder().setRegionInfo(serverRegion).build();
regions.put(defaultServerName, new RegionData(parsedRegionQuery, Base64.getEncoder().encodeToString(parsedRegionQuery.toByteString().toByteArray()))); regions.put(defaultServerName, new RegionData(parsedRegionQuery,
Base64.getEncoder().encodeToString(parsedRegionQuery.toByteString().toByteArray())));
} else { } else {
if(Grasscutter.getConfig().getDispatchOptions().getGameServers().length == 0) { if (Grasscutter.getConfig().getDispatchOptions().getGameServers().length == 0) {
Grasscutter.getLogger().error("[Dispatch] There are no game servers available. Exiting due to unplayable state."); Grasscutter.getLogger()
.error("[Dispatch] There are no game servers available. Exiting due to unplayable state.");
System.exit(1); System.exit(1);
} }
} }
for (Config.DispatchServerOptions.RegionInfo regionInfo : Grasscutter.getConfig().getDispatchOptions().getGameServers()) { for (Config.DispatchServerOptions.RegionInfo regionInfo : Grasscutter.getConfig().getDispatchOptions()
if(usedNames.contains(regionInfo.Name)) { .getGameServers()) {
if (usedNames.contains(regionInfo.Name)) {
Grasscutter.getLogger().error("Region name already in use."); Grasscutter.getLogger().error("Region name already in use.");
continue; continue;
} }
...@@ -133,7 +153,12 @@ public final class DispatchServer { ...@@ -133,7 +153,12 @@ public final class DispatchServer {
.setName(regionInfo.Name) .setName(regionInfo.Name)
.setTitle(regionInfo.Title) .setTitle(regionInfo.Title)
.setType("DEV_PUBLIC") .setType("DEV_PUBLIC")
.setDispatchUrl("http" + (Grasscutter.getConfig().getDispatchOptions().FrontHTTPS ? "s" : "") + "://" + (Grasscutter.getConfig().getDispatchOptions().PublicIp.isEmpty() ? Grasscutter.getConfig().getDispatchOptions().Ip : Grasscutter.getConfig().getDispatchOptions().PublicIp) + ":" + getAddress().getPort() + "/query_cur_region_" + regionInfo.Name) .setDispatchUrl(
"http" + (Grasscutter.getConfig().getDispatchOptions().FrontHTTPS ? "s" : "") + "://"
+ (Grasscutter.getConfig().getDispatchOptions().PublicIp.isEmpty()
? Grasscutter.getConfig().getDispatchOptions().Ip
: Grasscutter.getConfig().getDispatchOptions().PublicIp)
+ ":" + getAddress().getPort() + "/query_cur_region_" + regionInfo.Name)
.build(); .build();
usedNames.add(regionInfo.Name); usedNames.add(regionInfo.Name);
servers.add(server); servers.add(server);
...@@ -141,19 +166,21 @@ public final class DispatchServer { ...@@ -141,19 +166,21 @@ public final class DispatchServer {
RegionInfo serverRegion = regionQuery.getRegionInfo().toBuilder() RegionInfo serverRegion = regionQuery.getRegionInfo().toBuilder()
.setIp(regionInfo.Ip) .setIp(regionInfo.Ip)
.setPort(regionInfo.Port) .setPort(regionInfo.Port)
.setSecretKey(ByteString.copyFrom(FileUtils.read(Grasscutter.getConfig().KEY_FOLDER + "dispatchSeed.bin"))) .setSecretKey(ByteString
.copyFrom(FileUtils.read(Grasscutter.getConfig().KEY_FOLDER + "dispatchSeed.bin")))
.build(); .build();
QueryCurrRegionHttpRsp parsedRegionQuery = regionQuery.toBuilder().setRegionInfo(serverRegion).build(); QueryCurrRegionHttpRsp parsedRegionQuery = regionQuery.toBuilder().setRegionInfo(serverRegion).build();
regions.put(regionInfo.Name, new RegionData(parsedRegionQuery, Base64.getEncoder().encodeToString(parsedRegionQuery.toByteString().toByteArray()))); regions.put(regionInfo.Name, new RegionData(parsedRegionQuery,
Base64.getEncoder().encodeToString(parsedRegionQuery.toByteString().toByteArray())));
} }
QueryRegionListHttpRsp regionList = QueryRegionListHttpRsp.newBuilder() QueryRegionListHttpRsp regionList = QueryRegionListHttpRsp.newBuilder()
.addAllServers(servers) .addAllServers(servers)
.setClientSecretKey(rl.getClientSecretKey()) .setClientSecretKey(rl.getClientSecretKey())
.setClientCustomConfigEncrypted(rl.getClientCustomConfigEncrypted()) .setClientCustomConfigEncrypted(rl.getClientCustomConfigEncrypted())
.setEnableLoginPc(true) .setEnableLoginPc(true)
.build(); .build();
this.regionListBase64 = Base64.getEncoder().encodeToString(regionList.toByteString().toByteArray()); this.regionListBase64 = Base64.getEncoder().encodeToString(regionList.toByteString().toByteArray());
} catch (Exception e) { } catch (Exception e) {
...@@ -161,52 +188,92 @@ public final class DispatchServer { ...@@ -161,52 +188,92 @@ public final class DispatchServer {
} }
} }
private HttpServer safelyCreateServer(InetSocketAddress address) {
try {
return HttpServer.create(address, 0);
} catch (BindException ignored) {
Grasscutter.getLogger().error("Unable to bind to port: " + getAddress().getPort() + " (HTTP)");
} catch (Exception exception) {
Grasscutter.getLogger().error("Unable to start HTTP server.", exception);
}
return null;
}
public void start() throws Exception { public void start() throws Exception {
HttpServer server; HttpServer server;
if (Grasscutter.getConfig().getDispatchOptions().UseSSL) { if (Grasscutter.getConfig().getDispatchOptions().UseSSL) {
HttpsServer httpsServer; HttpsServer httpsServer = HttpsServer.create(getAddress(), 0);
httpsServer = HttpsServer.create(getAddress(), 0);
SSLContext sslContext = SSLContext.getInstance("TLS"); SSLContext sslContext = SSLContext.getInstance("TLS");
try (FileInputStream fis = new FileInputStream(Grasscutter.getConfig().getDispatchOptions().KeystorePath)) { try (FileInputStream fis = new FileInputStream(Grasscutter.getConfig().getDispatchOptions().KeystorePath)) {
char[] keystorePassword = Grasscutter.getConfig().getDispatchOptions().KeystorePassword.toCharArray(); char[] keystorePassword = Grasscutter.getConfig().getDispatchOptions().KeystorePassword.toCharArray();
KeyStore ks = KeyStore.getInstance("PKCS12"); KeyManagerFactory _kmf;
ks.load(fis, keystorePassword); try {
KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509"); KeyStore ks = KeyStore.getInstance("PKCS12");
kmf.init(ks, keystorePassword); ks.load(fis, keystorePassword);
KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
sslContext.init(kmf.getKeyManagers(), null, null); _kmf = kmf;
kmf.init(ks, keystorePassword);
} catch (Exception originalEx) {
try {
// try to initialize kmf with the default password
char[] defaultPassword = "123456".toCharArray();
Grasscutter.getLogger()
.warn("[Dispatch] Unable to load keystore. Trying default keystore password...");
KeyStore ks = KeyStore.getInstance("PKCS12");
ks.load(fis, defaultPassword);
KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
kmf.init(ks, defaultPassword);
_kmf = kmf;
Grasscutter.getLogger().warn(
"[Dispatch] The default keystore password was loaded successfully. Please consider setting the password in config.json.");
} catch (Exception ignored) {
Grasscutter.getLogger().warn("[Dispatch] Error while loading keystore!");
// don't care about the exception for the "123456" default password attempt
originalEx.printStackTrace();
throw originalEx;
}
}
sslContext.init(_kmf.getKeyManagers(), null, null);
httpsServer.setHttpsConfigurator(new HttpsConfigurator(sslContext)); httpsServer.setHttpsConfigurator(new HttpsConfigurator(sslContext));
server = httpsServer; server = httpsServer;
} catch (BindException ignored) {
Grasscutter.getLogger().error("Unable to bind to port: " + getAddress().getPort() + " (HTTPS)");
server = this.safelyCreateServer(this.getAddress());
} catch (Exception e) { } catch (Exception e) {
Grasscutter.getLogger().warn("[Dispatch] No SSL cert found! Falling back to HTTP server."); Grasscutter.getLogger().warn("[Dispatch] No SSL cert found! Falling back to HTTP server.");
Grasscutter.getConfig().getDispatchOptions().UseSSL = false; Grasscutter.getConfig().getDispatchOptions().UseSSL = false;
server = HttpServer.create(getAddress(), 0); server = this.safelyCreateServer(this.getAddress());
} }
} else { } else {
server = HttpServer.create(getAddress(), 0); server = this.safelyCreateServer(this.getAddress());
} }
if (server == null)
throw new NullPointerException("An HTTP server was not created.");
server.createContext("/", t -> responseHTML(t, "Hello")); server.createContext("/", t -> responseHTML(t, "Hello"));
// Dispatch // Dispatch
server.createContext("/query_region_list", t -> { server.createContext("/query_region_list", t -> {
// Log incoming request. // Log
Grasscutter.getLogger().info(String.format("[Dispatch] Client %s request: query_region_list", t.getRemoteAddress())); Grasscutter.getLogger()
.info(String.format("[Dispatch] Client %s request: query_region_list", t.getRemoteAddress()));
// Invoke event.
QueryAllRegionsEvent event = new QueryAllRegionsEvent(this.regionListBase64); event.call(); responseHTML(t, regionListBase64);
// Respond with event result.
responseHTML(t, event.getRegionList());
}); });
for (String regionName : regions.keySet()) { for (String regionName : regions.keySet()) {
server.createContext("/query_cur_region_" + regionName, t -> { server.createContext("/query_cur_region_" + regionName, t -> {
String regionCurrentBase64 = regions.get(regionName).Base64; String regionCurrentBase64 = regions.get(regionName).Base64;
// Log incoming request. // Log
Grasscutter.getLogger().info(String.format("Client %s request: query_cur_region_%s", t.getRemoteAddress(), regionName)); Grasscutter.getLogger().info(
String.format("Client %s request: query_cur_region_%s", t.getRemoteAddress(), regionName));
// Create a response from the request query parameters. // Create a response form the request query parameters
URI uri = t.getRequestURI(); URI uri = t.getRequestURI();
String response = "CAESGE5vdCBGb3VuZCB2ZXJzaW9uIGNvbmZpZw=="; String response = "CAESGE5vdCBGb3VuZCB2ZXJzaW9uIGNvbmZpZw==";
if (uri.getQuery() != null && uri.getQuery().length() > 0) { if (uri.getQuery() != null && uri.getQuery().length() > 0) {
...@@ -227,7 +294,8 @@ public final class DispatchServer { ...@@ -227,7 +294,8 @@ public final class DispatchServer {
try { try {
String body = Utils.toString(t.getRequestBody()); String body = Utils.toString(t.getRequestBody());
requestData = getGsonFactory().fromJson(body, LoginAccountRequestJson.class); requestData = getGsonFactory().fromJson(body, LoginAccountRequestJson.class);
} catch (Exception ignored) { } } catch (Exception ignored) {
}
// Create response json // Create response json
if (requestData == null) { if (requestData == null) {
...@@ -235,16 +303,19 @@ public final class DispatchServer { ...@@ -235,16 +303,19 @@ public final class DispatchServer {
} }
LoginResultJson responseData = new LoginResultJson(); LoginResultJson responseData = new LoginResultJson();
Grasscutter.getLogger().info(String.format("[Dispatch] Client %s is trying to log in", t.getRemoteAddress())); Grasscutter.getLogger()
.info(String.format("[Dispatch] Client %s is trying to log in", t.getRemoteAddress()));
// Login // Login
Account account = DatabaseHelper.getAccountByName(requestData.account); Account account = DatabaseHelper.getAccountByName(requestData.account);
// Check if account exists, else create a new one. // Check if account exists, else create a new one.
if (account == null) { if (account == null) {
// Account doesnt exist, so we can either auto create it if the config value is set // Account doesnt exist, so we can either auto create it if the config value is
// set
if (Grasscutter.getConfig().getDispatchOptions().AutomaticallyCreateAccounts) { if (Grasscutter.getConfig().getDispatchOptions().AutomaticallyCreateAccounts) {
// This account has been created AUTOMATICALLY. There will be no permissions added. // This account has been created AUTOMATICALLY. There will be no permissions
// added.
account = DatabaseHelper.createAccountWithId(requestData.account, 0); account = DatabaseHelper.createAccountWithId(requestData.account, 0);
if (account != null) { if (account != null) {
...@@ -253,19 +324,23 @@ public final class DispatchServer { ...@@ -253,19 +324,23 @@ public final class DispatchServer {
responseData.data.account.token = account.generateSessionKey(); responseData.data.account.token = account.generateSessionKey();
responseData.data.account.email = account.getEmail(); 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)); Grasscutter.getLogger()
.info(String.format("[Dispatch] Client %s failed to log in: Account %s created",
t.getRemoteAddress(), responseData.data.account.uid));
} else { } else {
responseData.retcode = -201; responseData.retcode = -201;
responseData.message = "Username not found, create failed."; responseData.message = "Username not found, create failed.";
Grasscutter.getLogger().info(String.format("[Dispatch] Client %s failed to log in: Account create failed", t.getRemoteAddress())); Grasscutter.getLogger().info(String.format(
"[Dispatch] Client %s failed to log in: Account create failed", t.getRemoteAddress()));
} }
} else { } else {
responseData.retcode = -201; responseData.retcode = -201;
responseData.message = "Username not found."; responseData.message = "Username not found.";
Grasscutter.getLogger().info(String.format("[Dispatch] Client %s failed to log in: Account no found", t.getRemoteAddress())); Grasscutter.getLogger().info(String
} .format("[Dispatch] Client %s failed to log in: Account no found", t.getRemoteAddress()));
}
} else { } else {
// Account was found, log the player in // Account was found, log the player in
responseData.message = "OK"; responseData.message = "OK";
...@@ -273,7 +348,8 @@ public final class DispatchServer { ...@@ -273,7 +348,8 @@ public final class DispatchServer {
responseData.data.account.token = account.generateSessionKey(); responseData.data.account.token = account.generateSessionKey();
responseData.data.account.email = account.getEmail(); responseData.data.account.email = account.getEmail();
Grasscutter.getLogger().info(String.format("[Dispatch] Client %s logged in as %s", t.getRemoteAddress(), responseData.data.account.uid)); Grasscutter.getLogger().info(String.format("[Dispatch] Client %s logged in as %s", t.getRemoteAddress(),
responseData.data.account.uid));
} }
responseJSON(t, responseData); responseJSON(t, responseData);
...@@ -285,31 +361,35 @@ public final class DispatchServer { ...@@ -285,31 +361,35 @@ public final class DispatchServer {
try { try {
String body = Utils.toString(t.getRequestBody()); String body = Utils.toString(t.getRequestBody());
requestData = getGsonFactory().fromJson(body, LoginTokenRequestJson.class); requestData = getGsonFactory().fromJson(body, LoginTokenRequestJson.class);
} catch (Exception ignored) { } } catch (Exception ignored) {
}
// Create response json // Create response json
if (requestData == null) { if (requestData == null) {
return; return;
} }
LoginResultJson responseData = new LoginResultJson(); LoginResultJson responseData = new LoginResultJson();
Grasscutter.getLogger().info(String.format("[Dispatch] Client %s is trying to log in via token", t.getRemoteAddress())); Grasscutter.getLogger()
.info(String.format("[Dispatch] Client %s is trying to log in via token", t.getRemoteAddress()));
// Login // Login
Account account = DatabaseHelper.getAccountById(requestData.uid); Account account = DatabaseHelper.getAccountById(requestData.uid);
// Test // Test
if (account == null || !account.getSessionKey().equals(requestData.token)) { if (account == null || !account.getSessionKey().equals(requestData.token)) {
responseData.retcode = -111; responseData.retcode = -111;
responseData.message = "Game account cache information error"; responseData.message = "Game account cache information error";
Grasscutter.getLogger().info(String.format("[Dispatch] Client %s failed to log in via token", t.getRemoteAddress())); Grasscutter.getLogger()
.info(String.format("[Dispatch] Client %s failed to log in via token", t.getRemoteAddress()));
} else { } else {
responseData.message = "OK"; responseData.message = "OK";
responseData.data.account.uid = requestData.uid; responseData.data.account.uid = requestData.uid;
responseData.data.account.token = requestData.token; responseData.data.account.token = requestData.token;
responseData.data.account.email = account.getEmail(); 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)); Grasscutter.getLogger().info(String.format("[Dispatch] Client %s logged in via token as %s",
t.getRemoteAddress(), responseData.data.account.uid));
} }
responseJSON(t, responseData); responseJSON(t, responseData);
...@@ -321,113 +401,112 @@ public final class DispatchServer { ...@@ -321,113 +401,112 @@ public final class DispatchServer {
try { try {
String body = Utils.toString(t.getRequestBody()); String body = Utils.toString(t.getRequestBody());
requestData = getGsonFactory().fromJson(body, ComboTokenReqJson.class); requestData = getGsonFactory().fromJson(body, ComboTokenReqJson.class);
} catch (Exception ignored) { } } catch (Exception ignored) {
}
// Create response json // Create response json
if (requestData == null || requestData.data == null) { if (requestData == null || requestData.data == null) {
return; return;
} }
LoginTokenData loginData = getGsonFactory().fromJson(requestData.data, LoginTokenData.class); // Get login data LoginTokenData loginData = getGsonFactory().fromJson(requestData.data, LoginTokenData.class); // Get login
// data
ComboTokenResJson responseData = new ComboTokenResJson(); ComboTokenResJson responseData = new ComboTokenResJson();
// Login // Login
Account account = DatabaseHelper.getAccountById(loginData.uid); Account account = DatabaseHelper.getAccountById(loginData.uid);
// Test // Test
if (account == null || !account.getSessionKey().equals(loginData.token)) { if (account == null || !account.getSessionKey().equals(loginData.token)) {
responseData.retcode = -201; responseData.retcode = -201;
responseData.message = "Wrong session key."; responseData.message = "Wrong session key.";
Grasscutter.getLogger().info(String.format("[Dispatch] Client %s failed to exchange combo token", t.getRemoteAddress())); Grasscutter.getLogger().info(
String.format("[Dispatch] Client %s failed to exchange combo token", t.getRemoteAddress()));
} else { } else {
responseData.message = "OK"; responseData.message = "OK";
responseData.data.open_id = loginData.uid; responseData.data.open_id = loginData.uid;
responseData.data.combo_id = "157795300"; responseData.data.combo_id = "157795300";
responseData.data.combo_token = account.generateLoginToken(); responseData.data.combo_token = account.generateLoginToken();
Grasscutter.getLogger().info(String.format("[Dispatch] Client %s succeed to exchange combo token", t.getRemoteAddress())); Grasscutter.getLogger().info(
String.format("[Dispatch] Client %s succeed to exchange combo token", t.getRemoteAddress()));
} }
responseJSON(t, responseData); responseJSON(t, responseData);
}); });
// Agreement and Protocol // Agreement and Protocol
server.createContext( // hk4e-sdk-os.hoyoverse.com server.createContext( // hk4e-sdk-os.hoyoverse.com
"/hk4e_global/mdk/agreement/api/getAgreementInfos", "/hk4e_global/mdk/agreement/api/getAgreementInfos",
new DispatchHttpJsonHandler("{\"retcode\":0,\"message\":\"OK\",\"data\":{\"marketing_agreements\":[]}}") new DispatchHttpJsonHandler(
); "{\"retcode\":0,\"message\":\"OK\",\"data\":{\"marketing_agreements\":[]}}"));
server.createContext( // hk4e-sdk-os.hoyoverse.com server.createContext( // hk4e-sdk-os.hoyoverse.com
"/hk4e_global/combo/granter/api/compareProtocolVersion", "/hk4e_global/combo/granter/api/compareProtocolVersion",
new DispatchHttpJsonHandler("{\"retcode\":0,\"message\":\"OK\",\"data\":{\"modified\":true,\"protocol\":{\"id\":0,\"app_id\":4,\"language\":\"en\",\"user_proto\":\"\",\"priv_proto\":\"\",\"major\":7,\"minimum\":0,\"create_time\":\"0\",\"teenager_proto\":\"\",\"third_proto\":\"\"}}}") new DispatchHttpJsonHandler(
); "{\"retcode\":0,\"message\":\"OK\",\"data\":{\"modified\":true,\"protocol\":{\"id\":0,\"app_id\":4,\"language\":\"en\",\"user_proto\":\"\",\"priv_proto\":\"\",\"major\":7,\"minimum\":0,\"create_time\":\"0\",\"teenager_proto\":\"\",\"third_proto\":\"\"}}}"));
// Game data // Game data
server.createContext( // hk4e-api-os.hoyoverse.com server.createContext( // hk4e-api-os.hoyoverse.com
"/common/hk4e_global/announcement/api/getAlertPic", "/common/hk4e_global/announcement/api/getAlertPic",
new DispatchHttpJsonHandler("{\"retcode\":0,\"message\":\"OK\",\"data\":{\"total\":0,\"list\":[]}}") new DispatchHttpJsonHandler("{\"retcode\":0,\"message\":\"OK\",\"data\":{\"total\":0,\"list\":[]}}"));
);
server.createContext( // hk4e-api-os.hoyoverse.com server.createContext( // hk4e-api-os.hoyoverse.com
"/common/hk4e_global/announcement/api/getAlertAnn", "/common/hk4e_global/announcement/api/getAlertAnn",
new DispatchHttpJsonHandler("{\"retcode\":0,\"message\":\"OK\",\"data\":{\"alert\":false,\"alert_id\":0,\"remind\":true}}") new DispatchHttpJsonHandler(
); "{\"retcode\":0,\"message\":\"OK\",\"data\":{\"alert\":false,\"alert_id\":0,\"remind\":true}}"));
server.createContext( // hk4e-api-os.hoyoverse.com server.createContext( // hk4e-api-os.hoyoverse.com
"/common/hk4e_global/announcement/api/getAnnList", "/common/hk4e_global/announcement/api/getAnnList",
new DispatchHttpJsonHandler("{\"retcode\":0,\"message\":\"OK\",\"data\":{\"list\":[],\"total\":0,\"type_list\":[],\"alert\":false,\"alert_id\":0,\"timezone\":0,\"t\":\"" + System.currentTimeMillis() + "\"}}") new DispatchHttpJsonHandler(
); "{\"retcode\":0,\"message\":\"OK\",\"data\":{\"list\":[],\"total\":0,\"type_list\":[],\"alert\":false,\"alert_id\":0,\"timezone\":0,\"t\":\""
+ System.currentTimeMillis() + "\"}}"));
server.createContext( // hk4e-api-os-static.hoyoverse.com server.createContext( // hk4e-api-os-static.hoyoverse.com
"/common/hk4e_global/announcement/api/getAnnContent", "/common/hk4e_global/announcement/api/getAnnContent",
new DispatchHttpJsonHandler("{\"retcode\":0,\"message\":\"OK\",\"data\":{\"list\":[],\"total\":0}}") new DispatchHttpJsonHandler("{\"retcode\":0,\"message\":\"OK\",\"data\":{\"list\":[],\"total\":0}}"));
);
server.createContext( // hk4e-sdk-os.hoyoverse.com server.createContext( // hk4e-sdk-os.hoyoverse.com
"/hk4e_global/mdk/shopwindow/shopwindow/listPriceTier", "/hk4e_global/mdk/shopwindow/shopwindow/listPriceTier",
new DispatchHttpJsonHandler("{\"retcode\":0,\"message\":\"OK\",\"data\":{\"suggest_currency\":\"USD\",\"tiers\":[]}}") new DispatchHttpJsonHandler(
); "{\"retcode\":0,\"message\":\"OK\",\"data\":{\"suggest_currency\":\"USD\",\"tiers\":[]}}"));
// Captcha // Captcha
server.createContext( // api-account-os.hoyoverse.com server.createContext( // api-account-os.hoyoverse.com
"/account/risky/api/check", "/account/risky/api/check",
new DispatchHttpJsonHandler("{\"retcode\":0,\"message\":\"OK\",\"data\":{\"id\":\"c8820f246a5241ab9973f71df3ddd791\",\"action\":\"\",\"geetest\":{\"challenge\":\"\",\"gt\":\"\",\"new_captcha\":0,\"success\":1}}}") new DispatchHttpJsonHandler(
); "{\"retcode\":0,\"message\":\"OK\",\"data\":{\"id\":\"c8820f246a5241ab9973f71df3ddd791\",\"action\":\"\",\"geetest\":{\"challenge\":\"\",\"gt\":\"\",\"new_captcha\":0,\"success\":1}}}"));
// Config // Config
server.createContext( // sdk-os-static.hoyoverse.com server.createContext( // sdk-os-static.hoyoverse.com
"/combo/box/api/config/sdk/combo", "/combo/box/api/config/sdk/combo",
new DispatchHttpJsonHandler("{\"retcode\":0,\"message\":\"OK\",\"data\":{\"vals\":{\"disable_email_bind_skip\":\"false\",\"email_bind_remind_interval\":\"7\",\"email_bind_remind\":\"true\"}}}") new DispatchHttpJsonHandler(
); "{\"retcode\":0,\"message\":\"OK\",\"data\":{\"vals\":{\"disable_email_bind_skip\":\"false\",\"email_bind_remind_interval\":\"7\",\"email_bind_remind\":\"true\"}}}"));
server.createContext( // hk4e-sdk-os-static.hoyoverse.com server.createContext( // hk4e-sdk-os-static.hoyoverse.com
"/hk4e_global/combo/granter/api/getConfig", "/hk4e_global/combo/granter/api/getConfig",
new DispatchHttpJsonHandler("{\"retcode\":0,\"message\":\"OK\",\"data\":{\"protocol\":true,\"qr_enabled\":false,\"log_level\":\"INFO\",\"announce_url\":\"https://webstatic-sea.hoyoverse.com/hk4e/announcement/index.html?sdk_presentation_style=fullscreen\\u0026sdk_screen_transparent=true\\u0026game_biz=hk4e_global\\u0026auth_appid=announcement\\u0026game=hk4e#/\",\"push_alias_type\":2,\"disable_ysdk_guard\":false,\"enable_announce_pic_popup\":true}}") new DispatchHttpJsonHandler(
); "{\"retcode\":0,\"message\":\"OK\",\"data\":{\"protocol\":true,\"qr_enabled\":false,\"log_level\":\"INFO\",\"announce_url\":\"https://webstatic-sea.hoyoverse.com/hk4e/announcement/index.html?sdk_presentation_style=fullscreen\\u0026sdk_screen_transparent=true\\u0026game_biz=hk4e_global\\u0026auth_appid=announcement\\u0026game=hk4e#/\",\"push_alias_type\":2,\"disable_ysdk_guard\":false,\"enable_announce_pic_popup\":true}}"));
server.createContext( // hk4e-sdk-os-static.hoyoverse.com server.createContext( // hk4e-sdk-os-static.hoyoverse.com
"/hk4e_global/mdk/shield/api/loadConfig", "/hk4e_global/mdk/shield/api/loadConfig",
new DispatchHttpJsonHandler("{\"retcode\":0,\"message\":\"OK\",\"data\":{\"id\":6,\"game_key\":\"hk4e_global\",\"client\":\"PC\",\"identity\":\"I_IDENTITY\",\"guest\":false,\"ignore_versions\":\"\",\"scene\":\"S_NORMAL\",\"name\":\"原神海外\",\"disable_regist\":false,\"enable_email_captcha\":false,\"thirdparty\":[\"fb\",\"tw\"],\"disable_mmt\":false,\"server_guest\":false,\"thirdparty_ignore\":{\"tw\":\"\",\"fb\":\"\"},\"enable_ps_bind_account\":false,\"thirdparty_login_configs\":{\"tw\":{\"token_type\":\"TK_GAME_TOKEN\",\"game_token_expires_in\":604800},\"fb\":{\"token_type\":\"TK_GAME_TOKEN\",\"game_token_expires_in\":604800}}}}") new DispatchHttpJsonHandler(
); "{\"retcode\":0,\"message\":\"OK\",\"data\":{\"id\":6,\"game_key\":\"hk4e_global\",\"client\":\"PC\",\"identity\":\"I_IDENTITY\",\"guest\":false,\"ignore_versions\":\"\",\"scene\":\"S_NORMAL\",\"name\":\"原神海外\",\"disable_regist\":false,\"enable_email_captcha\":false,\"thirdparty\":[\"fb\",\"tw\"],\"disable_mmt\":false,\"server_guest\":false,\"thirdparty_ignore\":{\"tw\":\"\",\"fb\":\"\"},\"enable_ps_bind_account\":false,\"thirdparty_login_configs\":{\"tw\":{\"token_type\":\"TK_GAME_TOKEN\",\"game_token_expires_in\":604800},\"fb\":{\"token_type\":\"TK_GAME_TOKEN\",\"game_token_expires_in\":604800}}}}"));
// Test api? // Test api?
server.createContext( // abtest-api-data-sg.hoyoverse.com server.createContext( // abtest-api-data-sg.hoyoverse.com
"/data_abtest_api/config/experiment/list", "/data_abtest_api/config/experiment/list",
new DispatchHttpJsonHandler("{\"retcode\":0,\"success\":true,\"message\":\"\",\"data\":[{\"code\":1000,\"type\":2,\"config_id\":\"14\",\"period_id\":\"6036_99\",\"version\":\"1\",\"configs\":{\"cardType\":\"old\"}}]}") new DispatchHttpJsonHandler(
); "{\"retcode\":0,\"success\":true,\"message\":\"\",\"data\":[{\"code\":1000,\"type\":2,\"config_id\":\"14\",\"period_id\":\"6036_99\",\"version\":\"1\",\"configs\":{\"cardType\":\"old\"}}]}"));
// Log Server // Log Server
server.createContext( // log-upload-os.mihoyo.com server.createContext( // log-upload-os.mihoyo.com
"/log/sdk/upload", "/log/sdk/upload",
new DispatchHttpJsonHandler("{\"code\":0}") new DispatchHttpJsonHandler("{\"code\":0}"));
);
server.createContext( // log-upload-os.mihoyo.com server.createContext( // log-upload-os.mihoyo.com
"/sdk/upload", "/sdk/upload",
new DispatchHttpJsonHandler("{\"code\":0}") new DispatchHttpJsonHandler("{\"code\":0}"));
);
server.createContext( // /perf/config/verify?device_id=xxx&platform=x&name=xxx server.createContext( // /perf/config/verify?device_id=xxx&platform=x&name=xxx
"/perf/config/verify", "/perf/config/verify",
new DispatchHttpJsonHandler("{\"code\":0}") new DispatchHttpJsonHandler("{\"code\":0}"));
);
// Logging servers // Logging servers
server.createContext( // overseauspider.yuanshen.com server.createContext( // overseauspider.yuanshen.com
"/log", "/log",
new DispatchHttpJsonHandler("{\"code\":0}") new DispatchHttpJsonHandler("{\"code\":0}"));
);
server.createContext( // log-upload-os.mihoyo.com server.createContext( // log-upload-os.mihoyo.com
"/crash/dataUpload", "/crash/dataUpload",
new DispatchHttpJsonHandler("{\"code\":0}") new DispatchHttpJsonHandler("{\"code\":0}"));
); server.createContext("/gacha", t -> responseHTML(t,
server.createContext("/gacha", t -> responseHTML(t, "<!doctype html><html lang=\"en\"><head><title>Gacha</title></head><body></body></html>")); "<!doctype html><html lang=\"en\"><head><title>Gacha</title></head><body></body></html>"));
// Start server // Start server
server.start(); server.start();
...@@ -450,12 +529,12 @@ public final class DispatchServer { ...@@ -450,12 +529,12 @@ public final class DispatchServer {
// Set the response header status and length // Set the response header status and length
t.getResponseHeaders().put("Content-Type", Collections.singletonList("text/html; charset=UTF-8")); t.getResponseHeaders().put("Content-Type", Collections.singletonList("text/html; charset=UTF-8"));
t.sendResponseHeaders(200, response.getBytes().length); t.sendResponseHeaders(200, response.getBytes().length);
//Write the response string // Write the response string
OutputStream os = t.getResponseBody(); OutputStream os = t.getResponseBody();
os.write(response.getBytes()); os.write(response.getBytes());
os.close(); os.close();
} }
private Map<String, String> parseQueryString(String qs) { private Map<String, String> parseQueryString(String qs) {
Map<String, String> result = new HashMap<>(); Map<String, String> result = new HashMap<>();
if (qs == null) { if (qs == null) {
...@@ -475,7 +554,8 @@ public final class DispatchServer { ...@@ -475,7 +554,8 @@ public final class DispatchServer {
if (eqPos < 0 || eqPos > next) { if (eqPos < 0 || eqPos > next) {
result.put(URLDecoder.decode(qs.substring(last, next), "utf-8"), ""); result.put(URLDecoder.decode(qs.substring(last, next), "utf-8"), "");
} else { } else {
result.put(URLDecoder.decode(qs.substring(last, eqPos), "utf-8"), URLDecoder.decode(qs.substring(eqPos + 1, next), "utf-8")); result.put(URLDecoder.decode(qs.substring(last, eqPos), "utf-8"),
URLDecoder.decode(qs.substring(eqPos + 1, next), "utf-8"));
} }
} catch (UnsupportedEncodingException e) { } catch (UnsupportedEncodingException e) {
throw new RuntimeException(e); // will never happen, utf-8 support is mandatory for java throw new RuntimeException(e); // will never happen, utf-8 support is mandatory for java
......
package emu.grasscutter.server.packet.recv;
import emu.grasscutter.data.GenshinData;
import emu.grasscutter.data.def.RewardData;
import emu.grasscutter.game.avatar.GenshinAvatar;
import emu.grasscutter.game.inventory.GenshinItem;
import emu.grasscutter.game.props.ActionReason;
import emu.grasscutter.net.packet.Opcodes;
import emu.grasscutter.net.packet.PacketOpcodes;
import emu.grasscutter.net.proto.AvatarFetterLevelRewardReqOuterClass.AvatarFetterLevelRewardReq;
import emu.grasscutter.server.game.GameSession;
import emu.grasscutter.server.packet.send.PacketAvatarDataNotify;
import emu.grasscutter.server.packet.send.PacketAvatarFetterDataNotify;
import emu.grasscutter.server.packet.send.PacketAvatarFetterLevelRewardRsp;
import emu.grasscutter.server.packet.send.PacketItemAddHintNotify;
import emu.grasscutter.server.packet.send.PacketUnlockNameCardNotify;
import emu.grasscutter.net.packet.PacketHandler;
@Opcodes(PacketOpcodes.AvatarFetterLevelRewardReq)
public class HandlerAvatarFetterLevelRewardReq extends PacketHandler {
@Override
public void handle(GameSession session, byte[] header, byte[] payload) throws Exception {
AvatarFetterLevelRewardReq req = AvatarFetterLevelRewardReq.parseFrom(payload);
if (req.getFetterLevel() < 10) {
// You don't have a full level of fetter level, why do you want to get a divorce certificate?
session.send(new PacketAvatarFetterLevelRewardRsp(req.getAvatarGuid(), req.getFetterLevel()));
} else {
long avatarGuid = req.getAvatarGuid();
GenshinAvatar avatar = session
.getPlayer()
.getAvatars()
.getAvatarByGuid(avatarGuid);
int rewardId = avatar.getNameCardRewardId();
RewardData card = GenshinData.getRewardDataMap().get(rewardId);
int cardId = card.getRewardItemList().get(0).getItemId();
if (session.getPlayer().getNameCardList().contains(cardId)) {
// Already got divorce certificate.
session.getPlayer().sendPacket(new PacketAvatarFetterLevelRewardRsp(req.getAvatarGuid(), req.getFetterLevel(), rewardId));
return;
}
GenshinItem item = new GenshinItem(cardId);
session.getPlayer().getInventory().addItem(item);
session.getPlayer().sendPacket(new PacketItemAddHintNotify(item, ActionReason.FetterLevelReward));
session.getPlayer().sendPacket(new PacketUnlockNameCardNotify(cardId));
session.send(new PacketAvatarFetterDataNotify(avatar));
session.send(new PacketAvatarDataNotify(avatar.getPlayer()));
session.send(new PacketAvatarFetterLevelRewardRsp(avatarGuid, req.getFetterLevel(), rewardId));
}
}
}
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