Commit 7925d1cd authored by Melledy's avatar Melledy
Browse files

Initial commit

parents
package emu.grasscutter;
import java.util.Arrays;
import emu.grasscutter.game.props.OpenState;
import emu.grasscutter.utils.Position;
import emu.grasscutter.utils.Utils;
public class GenshinConstants {
public static String VERSION = "2.6.0";
public static final int MAX_TEAMS = 4;
public static final int MAX_AVATARS_IN_TEAM = 4;
public static final int LIMIT_WEAPON = 2000;
public static final int LIMIT_RELIC = 2000;
public static final int LIMIT_MATERIAL = 2000;
public static final int LIMIT_FURNITURE = 2000;
public static final int LIMIT_ALL = 30000;
public static final int MAIN_CHARACTER_MALE = 10000005;
public static final int MAIN_CHARACTER_FEMALE = 10000007;
public static final Position START_POSITION = new Position(2747, 194, -1719);
public static final int MAX_FRIENDS = 45;
public static final int MAX_FRIEND_REQUESTS = 50;
public static final int SERVER_CONSOLE_UID = 99; // uid of the fake player used for commands
// Default entity ability hashes
public static final String[] DEFAULT_ABILITY_STRINGS = {
"Avatar_DefaultAbility_VisionReplaceDieInvincible", "Avatar_DefaultAbility_AvartarInShaderChange", "Avatar_SprintBS_Invincible",
"Avatar_Freeze_Duration_Reducer", "Avatar_Attack_ReviveEnergy", "Avatar_Component_Initializer", "Avatar_FallAnthem_Achievement_Listener"
};
public static final int[] DEFAULT_ABILITY_HASHES = Arrays.stream(DEFAULT_ABILITY_STRINGS).mapToInt(Utils::abilityHash).toArray();
public static final int DEFAULT_ABILITY_NAME = Utils.abilityHash("Default");
}
package emu.grasscutter;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.InputStreamReader;
import java.net.InetSocketAddress;
import java.util.Arrays;
import org.slf4j.LoggerFactory;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import ch.qos.logback.classic.Logger;
import emu.grasscutter.commands.ServerCommands;
import emu.grasscutter.data.ResourceLoader;
import emu.grasscutter.database.DatabaseManager;
import emu.grasscutter.server.dispatch.DispatchServer;
import emu.grasscutter.server.game.GameServer;
import emu.grasscutter.tools.Tools;
import emu.grasscutter.utils.Crypto;
public class Grasscutter {
private static Logger log = (Logger) LoggerFactory.getLogger(Grasscutter.class);
private static Config config;
private static Gson gson = new GsonBuilder().setPrettyPrinting().create();
private static File configFile = new File("./config.json");
public static RunMode MODE = RunMode.BOTH;
private static DispatchServer dispatchServer;
private static GameServer gameServer;
public static void main(String[] args) throws Exception {
Grasscutter.loadConfig();
Crypto.loadKeys();
for (String arg : args) {
switch (arg.toLowerCase()) {
case "-auth":
MODE = RunMode.AUTH;
break;
case "-game":
MODE = RunMode.GAME;
break;
case "-handbook":
Tools.createGmHandbook();
return;
}
}
// Startup
Grasscutter.getLogger().info("Grasscutter Emu");
// Load resource files
ResourceLoader.loadAll();
// Database
DatabaseManager.initialize();
// Run servers
dispatchServer = new DispatchServer();
dispatchServer.start();
gameServer = new GameServer(new InetSocketAddress(getConfig().GameServerIp, getConfig().GameServerPort));
gameServer.start();
startConsole();
}
public static Config getConfig() {
return config;
}
public static Logger getLogger() {
return log;
}
public static Gson getGsonFactory() {
return gson;
}
public static DispatchServer getDispatchServer() {
return dispatchServer;
}
public static GameServer getGameServer() {
return gameServer;
}
public static void loadConfig() {
try (FileReader file = new FileReader(configFile)) {
config = gson.fromJson(file, Config.class);
} catch (Exception e) {
Grasscutter.config = new Config();
}
saveConfig();
}
public static void saveConfig() {
try (FileWriter file = new FileWriter(configFile)) {
file.write(gson.toJson(config));
} catch (Exception e) {
Grasscutter.getLogger().error("Config save error");
}
}
public static void startConsole() {
String input;
try (BufferedReader br = new BufferedReader(new InputStreamReader(System.in))) {
while ((input = br.readLine()) != null) {
ServerCommands.handle(input);
}
} catch (Exception e) {
Grasscutter.getLogger().error("Console error:", e);
}
}
public enum RunMode {
BOTH,
AUTH,
GAME
}
}
package emu.grasscutter.commands;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@Retention(RetentionPolicy.RUNTIME)
public @interface Command {
public String[] aliases() default "";
public int gmLevel() default 1;
public String helpText() default "";
}
package emu.grasscutter.commands;
import java.lang.reflect.Modifier;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import emu.grasscutter.data.GenshinData;
import emu.grasscutter.data.def.ItemData;
import emu.grasscutter.data.def.MonsterData;
import emu.grasscutter.game.GenshinPlayer;
import emu.grasscutter.game.avatar.GenshinAvatar;
import emu.grasscutter.game.entity.EntityAvatar;
import emu.grasscutter.game.entity.EntityItem;
import emu.grasscutter.game.entity.EntityMonster;
import emu.grasscutter.game.entity.GenshinEntity;
import emu.grasscutter.game.inventory.GenshinItem;
import emu.grasscutter.game.inventory.ItemType;
import emu.grasscutter.game.props.ActionReason;
import emu.grasscutter.game.props.FightProperty;
import emu.grasscutter.server.packet.send.PacketEntityFightPropUpdateNotify;
import emu.grasscutter.server.packet.send.PacketItemAddHintNotify;
import emu.grasscutter.utils.Position;
public class PlayerCommands {
private static HashMap<String, PlayerCommand> list = new HashMap<String, PlayerCommand>();
static {
try {
// Look for classes
for (Class<?> cls : PlayerCommands.class.getDeclaredClasses()) {
// Get non abstract classes
if (!Modifier.isAbstract(cls.getModifiers())) {
Command commandAnnotation = cls.getAnnotation(Command.class);
PlayerCommand command = (PlayerCommand) cls.newInstance();
if (commandAnnotation != null) {
command.setLevel(commandAnnotation.gmLevel());
for (String alias : commandAnnotation.aliases()) {
if (alias.length() == 0) {
continue;
}
String commandName = "!" + alias;
list.put(commandName, command);
commandName = "/" + alias;
list.put(commandName, command);
}
}
String commandName = "!" + cls.getSimpleName().toLowerCase();
list.put(commandName, command);
commandName = "/" + cls.getSimpleName().toLowerCase();
list.put(commandName, command);
}
}
} catch (Exception e) {
}
}
public static void handle(GenshinPlayer player, String msg) {
String[] split = msg.split(" ");
// End if invalid
if (split.length == 0) {
return;
}
//
String first = split[0].toLowerCase();
PlayerCommand c = PlayerCommands.list.get(first);
if (c != null) {
// Level check
if (player.getGmLevel() < c.getLevel()) {
return;
}
// Execute
int len = Math.min(first.length() + 1, msg.length());
c.execute(player, msg.substring(len));
}
}
public static abstract class PlayerCommand {
// GM level required to use this command
private int level;
protected int getLevel() { return this.level; }
protected void setLevel(int minLevel) { this.level = minLevel; }
// Main
public abstract void execute(GenshinPlayer player, String raw);
}
// ================ Commands ================
@Command(aliases = {"g", "item", "additem"}, helpText = "/give [item id] [count] - Gives {count} amount of {item id}")
public static class Give extends PlayerCommand {
@Override
public void execute(GenshinPlayer player, String raw) {
String[] split = raw.split(" ");
int itemId = 0, count = 1;
try {
itemId = Integer.parseInt(split[0]);
} catch (Exception e) {
itemId = 0;
}
try {
count = Math.max(Math.min(Integer.parseInt(split[1]), Integer.MAX_VALUE), 1);
} catch (Exception e) {
count = 1;
}
// Give
ItemData itemData = GenshinData.getItemDataMap().get(itemId);
GenshinItem item;
if (itemData == null) {
player.dropMessage("Error: Item data not found");
return;
}
if (itemData.isEquip()) {
List<GenshinItem> items = new LinkedList<>();
for (int i = 0; i < count; i++) {
item = new GenshinItem(itemData);
items.add(item);
}
player.getInventory().addItems(items);
player.sendPacket(new PacketItemAddHintNotify(items, ActionReason.SubfieldDrop));
} else {
item = new GenshinItem(itemData, count);
player.getInventory().addItem(item);
player.sendPacket(new PacketItemAddHintNotify(item, ActionReason.SubfieldDrop));
}
}
}
@Command(aliases = {"d"}, helpText = "/drop [item id] [count] - Drops {count} amount of {item id}")
public static class Drop extends PlayerCommand {
@Override
public void execute(GenshinPlayer player, String raw) {
String[] split = raw.split(" ");
int itemId = 0, count = 1;
try {
itemId = Integer.parseInt(split[0]);
} catch (Exception e) {
itemId = 0;
}
try {
count = Math.max(Math.min(Integer.parseInt(split[1]), Integer.MAX_VALUE), 1);
} catch (Exception e) {
count = 1;
}
// Give
ItemData itemData = GenshinData.getItemDataMap().get(itemId);
if (itemData == null) {
player.dropMessage("Error: Item data not found");
return;
}
if (itemData.isEquip()) {
float range = (5f + (.1f * count));
for (int i = 0; i < count; i++) {
Position pos = player.getPos().clone().addX((float) (Math.random() * range) - (range / 2)).addY(3f).addZ((float) (Math.random() * range) - (range / 2));
EntityItem entity = new EntityItem(player.getWorld(), player, itemData, pos, 1);
player.getWorld().addEntity(entity);
}
} else {
EntityItem entity = new EntityItem(player.getWorld(), player, itemData, player.getPos().clone().addY(3f), count);
player.getWorld().addEntity(entity);
}
}
}
@Command(helpText = "/spawn [monster id] [count] - Creates {count} amount of {item id}")
public static class Spawn extends PlayerCommand {
@Override
public void execute(GenshinPlayer player, String raw) {
String[] split = raw.split(" ");
int monsterId = 0, count = 1, level = 1;
try {
monsterId = Integer.parseInt(split[0]);
} catch (Exception e) {
monsterId = 0;
}
try {
level = Math.max(Math.min(Integer.parseInt(split[1]), 200), 1);
} catch (Exception e) {
level = 1;
}
try {
count = Math.max(Math.min(Integer.parseInt(split[2]), 1000), 1);
} catch (Exception e) {
count = 1;
}
// Give
MonsterData monsterData = GenshinData.getMonsterDataMap().get(monsterId);
if (monsterData == null) {
player.dropMessage("Error: Monster data not found");
return;
}
float range = (5f + (.1f * count));
for (int i = 0; i < count; i++) {
Position pos = player.getPos().clone().addX((float) (Math.random() * range) - (range / 2)).addY(3f).addZ((float) (Math.random() * range) - (range / 2));
EntityMonster entity = new EntityMonster(player.getWorld(), monsterData, pos, level);
player.getWorld().addEntity(entity);
}
}
}
@Command(helpText = "/killall")
public static class KillAll extends PlayerCommand {
@Override
public void execute(GenshinPlayer player, String raw) {
List<GenshinEntity> toRemove = new LinkedList<>();
for (GenshinEntity entity : player.getWorld().getEntities().values()) {
if (entity instanceof EntityMonster) {
toRemove.add(entity);
}
}
toRemove.forEach(e -> player.getWorld().killEntity(e, 0));
}
}
@Command(helpText = "/resetconst - Resets all constellations for the currently active character")
public static class ResetConst extends PlayerCommand {
@Override
public void execute(GenshinPlayer player, String raw) {
EntityAvatar entity = player.getTeamManager().getCurrentAvatarEntity();
if (entity == null) {
return;
}
GenshinAvatar avatar = entity.getAvatar();
avatar.getTalentIdList().clear();
avatar.setCoreProudSkillLevel(0);
avatar.recalcStats();
avatar.save();
player.dropMessage("Constellations for " + entity.getAvatar().getAvatarData().getName() + " have been reset. Please relogin to see changes.");
}
}
@Command(helpText = "/godmode - Prevents you from taking damage")
public static class Godmode extends PlayerCommand {
@Override
public void execute(GenshinPlayer player, String raw) {
player.setGodmode(!player.hasGodmode());
player.dropMessage("Godmode is now " + (player.hasGodmode() ? "ON" : "OFF"));
}
}
@Command(helpText = "/sethp [hp]")
public static class Sethp extends PlayerCommand {
@Override
public void execute(GenshinPlayer player, String raw) {
String[] split = raw.split(" ");
int hp = 0;
try {
hp = Math.max(Integer.parseInt(split[0]), 1);
} catch (Exception e) {
hp = 1;
}
EntityAvatar entity = player.getTeamManager().getCurrentAvatarEntity();
if (entity == null) {
return;
}
entity.setFightProperty(FightProperty.FIGHT_PROP_CUR_HP, hp);
entity.getWorld().broadcastPacket(new PacketEntityFightPropUpdateNotify(entity, FightProperty.FIGHT_PROP_CUR_HP));
}
}
@Command(aliases = {"clearart"}, helpText = "/clearartifacts")
public static class ClearArtifacts extends PlayerCommand {
@Override
public void execute(GenshinPlayer player, String raw) {
List<GenshinItem> toRemove = new LinkedList<>();
for (GenshinItem item : player.getInventory().getItems().values()) {
if (item.getItemType() == ItemType.ITEM_RELIQUARY && item.getLevel() == 1 && item.getExp() == 0 && !item.isLocked() && !item.isEquipped()) {
toRemove.add(item);
}
}
player.getInventory().removeItems(toRemove);
}
}
}
package emu.grasscutter.commands;
import java.lang.reflect.Modifier;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.data.GenshinData;
import emu.grasscutter.data.def.ItemData;
import emu.grasscutter.database.DatabaseHelper;
import emu.grasscutter.game.GenshinPlayer;
import emu.grasscutter.game.inventory.GenshinItem;
import emu.grasscutter.utils.Crypto;
import emu.grasscutter.utils.Utils;
public class ServerCommands {
private static HashMap<String, ServerCommand> list = new HashMap<>();
static {
try {
// Look for classes
for (Class<?> cls : ServerCommands.class.getDeclaredClasses()) {
// Get non abstract classes
if (!Modifier.isAbstract(cls.getModifiers())) {
String commandName = cls.getSimpleName().toLowerCase();
list.put(commandName, (ServerCommand) cls.newInstance());
}
}
} catch (Exception e) {
}
}
public static void handle(String msg) {
String[] split = msg.split(" ");
// End if invalid
if (split.length == 0) {
return;
}
//
String first = split[0].toLowerCase();
ServerCommand c = ServerCommands.list.get(first);
if (c != null) {
// Execute
int len = Math.min(first.length() + 1, msg.length());
c.execute(msg.substring(len));
}
}
public static abstract class ServerCommand {
public abstract void execute(String raw);
}
// ================ Commands ================
public static class Reload extends ServerCommand {
@Override
public void execute(String raw) {
Grasscutter.getLogger().info("Reloading config.");
Grasscutter.loadConfig();
Grasscutter.getDispatchServer().loadQueries();
Grasscutter.getLogger().info("Reload complete.");
}
}
public static class Account extends ServerCommand {
@Override
public void execute(String raw) {
String[] split = raw.split(" ");
if (split.length < 2) {
Grasscutter.getLogger().error("Invalid amount of args");
return;
}
String command = split[0].toLowerCase();
String username = split[1];
switch (command) {
case "create":
if (split.length < 2) {
Grasscutter.getLogger().error("Invalid amount of args");
return;
}
int reservedId = 0;
try {
reservedId = Integer.parseInt(split[2]);
} catch (Exception e) {
reservedId = 0;
}
emu.grasscutter.game.Account account = DatabaseHelper.createAccountWithId(username, reservedId);
if (account != null) {
Grasscutter.getLogger().info("Account created" + (reservedId > 0 ? " with an id of " + reservedId : ""));
} else {
Grasscutter.getLogger().error("Account already exists");
}
break;
case "delete":
boolean success = DatabaseHelper.deleteAccount(username);
if (success) {
Grasscutter.getLogger().info("Account deleted");
}
break;
/*
case "setpw":
case "setpass":
case "setpassword":
if (split.length < 3) {
Grasscutter.getLogger().error("Invalid amount of args");
return;
}
account = DatabaseHelper.getAccountByName(username);
if (account == null) {
Grasscutter.getLogger().error("No account found!");
return;
}
token = split[2];
token = PasswordHelper.hashPassword(token);
account.setPassword(token);
DatabaseHelper.saveAccount(account);
Grasscutter.getLogger().info("Password set");
break;
*/
}
}
}
}
package emu.grasscutter.data;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.utils.Utils;
import emu.grasscutter.data.custom.AbilityEmbryoEntry;
import emu.grasscutter.data.custom.OpenConfigEntry;
import emu.grasscutter.data.def.*;
import it.unimi.dsi.fastutil.ints.Int2ObjectLinkedOpenHashMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
public class GenshinData {
// BinOutputs
private static final Int2ObjectMap<String> abilityHashes = new Int2ObjectOpenHashMap<>();
private static final Map<String, AbilityEmbryoEntry> abilityEmbryos = new HashMap<>();
private static final Map<String, OpenConfigEntry> openConfigEntries = new HashMap<>();
// ExcelConfigs
private static final Int2ObjectMap<PlayerLevelData> playerLevelDataMap = new Int2ObjectOpenHashMap<>();
private static final Int2ObjectMap<AvatarData> avatarDataMap = new Int2ObjectOpenHashMap<>();
private static final Int2ObjectMap<AvatarLevelData> avatarLevelDataMap = new Int2ObjectOpenHashMap<>();
private static final Int2ObjectMap<AvatarSkillDepotData> avatarSkillDepotDataMap = new Int2ObjectOpenHashMap<>();
private static final Int2ObjectMap<AvatarSkillData> avatarSkillDataMap = new Int2ObjectOpenHashMap<>();
private static final Int2ObjectMap<AvatarCurveData> avatarCurveDataMap = new Int2ObjectLinkedOpenHashMap<>();
private static final Int2ObjectMap<AvatarPromoteData> avatarPromoteDataMap = new Int2ObjectOpenHashMap<>();
private static final Int2ObjectMap<AvatarTalentData> avatarTalentDataMap = new Int2ObjectOpenHashMap<>();
private static final Int2ObjectMap<ProudSkillData> proudSkillDataMap = new Int2ObjectOpenHashMap<>();
private static final Int2ObjectMap<ItemData> itemDataMap = new Int2ObjectOpenHashMap<>();
private static final Int2ObjectMap<ReliquaryLevelData> reliquaryLevelDataMap = new Int2ObjectOpenHashMap<>();
private static final Int2ObjectMap<ReliquaryAffixData> reliquaryAffixDataMap = new Int2ObjectOpenHashMap<>();
private static final Int2ObjectMap<ReliquaryMainPropData> reliquaryMainPropDataMap = new Int2ObjectOpenHashMap<>();
private static final Int2ObjectMap<ReliquarySetData> reliquarySetDataMap = new Int2ObjectOpenHashMap<>();
private static final Int2ObjectMap<WeaponLevelData> weaponLevelDataMap = new Int2ObjectOpenHashMap<>();
private static final Int2ObjectMap<WeaponPromoteData> weaponPromoteDataMap = new Int2ObjectOpenHashMap<>();
private static final Int2ObjectMap<WeaponCurveData> weaponCurveDataMap = new Int2ObjectOpenHashMap<>();
private static final Int2ObjectMap<EquipAffixData> equipAffixDataMap = new Int2ObjectOpenHashMap<>();
private static final Int2ObjectMap<MonsterData> monsterDataMap = new Int2ObjectOpenHashMap<>();
private static final Int2ObjectMap<NpcData> npcDataMap = new Int2ObjectOpenHashMap<>();
private static final Int2ObjectMap<GadgetData> gadgetDataMap = new Int2ObjectOpenHashMap<>();
private static final Int2ObjectMap<MonsterCurveData> monsterCurveDataMap = new Int2ObjectOpenHashMap<>();
private static final Int2ObjectMap<MonsterDescribeData> monsterDescribeDataMap = new Int2ObjectOpenHashMap<>();
private static final Int2ObjectMap<AvatarFlycloakData> avatarFlycloakDataMap = new Int2ObjectLinkedOpenHashMap<>();
private static final Int2ObjectMap<AvatarCostumeData> avatarCostumeDataMap = new Int2ObjectLinkedOpenHashMap<>();
private static final Int2ObjectMap<AvatarCostumeData> avatarCostumeDataItemIdMap = new Int2ObjectLinkedOpenHashMap<>();
public static Int2ObjectMap<?> getMapByResourceDef(Class<?> resourceDefinition) {
Int2ObjectMap<?> map = null;
try {
Field field = GenshinData.class.getDeclaredField(Utils.lowerCaseFirstChar(resourceDefinition.getSimpleName()) + "Map");
field.setAccessible(true);
map = (Int2ObjectMap<?>) field.get(null);
field.setAccessible(false);
} catch (Exception e) {
Grasscutter.getLogger().error("Error fetching resource map for " + resourceDefinition.getSimpleName(), e);
}
return map;
}
public static Int2ObjectMap<String> getAbilityHashes() {
return abilityHashes;
}
public static Map<String, AbilityEmbryoEntry> getAbilityEmbryoInfo() {
return abilityEmbryos;
}
public static Map<String, OpenConfigEntry> getOpenConfigEntries() {
return openConfigEntries;
}
public static Int2ObjectMap<AvatarData> getAvatarDataMap() {
return avatarDataMap;
}
public static Int2ObjectMap<ItemData> getItemDataMap() {
return itemDataMap;
}
public static Int2ObjectMap<AvatarSkillDepotData> getAvatarSkillDepotDataMap() {
return avatarSkillDepotDataMap;
}
public static Int2ObjectMap<AvatarSkillData> getAvatarSkillDataMap() {
return avatarSkillDataMap;
}
public static Int2ObjectMap<PlayerLevelData> getPlayerLevelDataMap() {
return playerLevelDataMap;
}
public static Int2ObjectMap<AvatarLevelData> getAvatarLevelDataMap() {
return avatarLevelDataMap;
}
public static Int2ObjectMap<WeaponLevelData> getWeaponLevelDataMap() {
return weaponLevelDataMap;
}
public static Int2ObjectMap<ReliquaryAffixData> getReliquaryAffixDataMap() {
return reliquaryAffixDataMap;
}
public static Int2ObjectMap<ReliquaryMainPropData> getReliquaryMainPropDataMap() {
return reliquaryMainPropDataMap;
}
public static Int2ObjectMap<WeaponPromoteData> getWeaponPromoteDataMap() {
return weaponPromoteDataMap;
}
public static Int2ObjectMap<WeaponCurveData> getWeaponCurveDataMap() {
return weaponCurveDataMap;
}
public static Int2ObjectMap<AvatarCurveData> getAvatarCurveDataMap() {
return avatarCurveDataMap;
}
public static int getRelicExpRequired(int rankLevel, int level) {
ReliquaryLevelData levelData = reliquaryLevelDataMap.get((rankLevel << 8) + level);
return levelData != null ? levelData.getExp() : 0;
}
public static ReliquaryLevelData getRelicLevelData(int rankLevel, int level) {
return reliquaryLevelDataMap.get((rankLevel << 8) + level);
}
public static WeaponPromoteData getWeaponPromoteData(int promoteId, int promoteLevel) {
return weaponPromoteDataMap.get((promoteId << 8) + promoteLevel);
}
public static int getWeaponExpRequired(int rankLevel, int level) {
WeaponLevelData levelData = weaponLevelDataMap.get(level);
if (levelData == null) {
return 0;
}
try {
return levelData.getRequiredExps()[rankLevel - 1];
} catch (Exception e) {
return 0;
}
}
public static AvatarPromoteData getAvatarPromoteData(int promoteId, int promoteLevel) {
return avatarPromoteDataMap.get((promoteId << 8) + promoteLevel);
}
public static int getAvatarLevelExpRequired(int level) {
AvatarLevelData levelData = avatarLevelDataMap.get(level);
return levelData != null ? levelData.getExp() : 0;
}
public static Int2ObjectMap<ProudSkillData> getProudSkillDataMap() {
return proudSkillDataMap;
}
public static Int2ObjectMap<MonsterData> getMonsterDataMap() {
return monsterDataMap;
}
public static Int2ObjectMap<NpcData> getNpcDataMap() {
return npcDataMap;
}
public static Int2ObjectMap<GadgetData> getGadgetDataMap() {
return gadgetDataMap;
}
public static Int2ObjectMap<ReliquarySetData> getReliquarySetDataMap() {
return reliquarySetDataMap;
}
public static Int2ObjectMap<EquipAffixData> getEquipAffixDataMap() {
return equipAffixDataMap;
}
public static Int2ObjectMap<MonsterCurveData> getMonsterCurveDataMap() {
return monsterCurveDataMap;
}
public static Int2ObjectMap<MonsterDescribeData> getMonsterDescribeDataMap() {
return monsterDescribeDataMap;
}
public static Int2ObjectMap<AvatarTalentData> getAvatarTalentDataMap() {
return avatarTalentDataMap;
}
public static Int2ObjectMap<AvatarFlycloakData> getAvatarFlycloakDataMap() {
return avatarFlycloakDataMap;
}
public static Int2ObjectMap<AvatarCostumeData> getAvatarCostumeDataMap() {
return avatarCostumeDataMap;
}
public static Int2ObjectMap<AvatarCostumeData> getAvatarCostumeDataItemIdMap() {
return avatarCostumeDataItemIdMap;
}
}
package emu.grasscutter.data;
import java.util.ArrayList;
import java.util.List;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.data.def.ReliquaryAffixData;
import emu.grasscutter.data.def.ReliquaryMainPropData;
import emu.grasscutter.utils.WeightedList;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
public class GenshinDepot {
private static Int2ObjectMap<WeightedList<ReliquaryMainPropData>> relicMainPropDepot = new Int2ObjectOpenHashMap<>();
private static Int2ObjectMap<List<ReliquaryAffixData>> relicAffixDepot = new Int2ObjectOpenHashMap<>();
public static void load() {
for (ReliquaryMainPropData data : GenshinData.getReliquaryMainPropDataMap().values()) {
if (data.getWeight() <= 0 || data.getPropDepotId() <= 0) {
continue;
}
WeightedList<ReliquaryMainPropData> list = relicMainPropDepot.computeIfAbsent(data.getPropDepotId(), k -> new WeightedList<>());
list.add(data.getWeight(), data);
}
for (ReliquaryAffixData data : GenshinData.getReliquaryAffixDataMap().values()) {
if (data.getWeight() <= 0 || data.getDepotId() <= 0) {
continue;
}
List<ReliquaryAffixData> list = relicAffixDepot.computeIfAbsent(data.getDepotId(), k -> new ArrayList<>());
list.add(data);
}
// Let the server owner know if theyre missing weights
if (relicMainPropDepot.size() == 0 || relicAffixDepot.size() == 0) {
Grasscutter.getLogger().error("Relic properties are missing weights! Please check your ReliquaryMainPropExcelConfigData or ReliquaryAffixExcelConfigData files in your ExcelBinOutput folder.");
}
}
public static ReliquaryMainPropData getRandomRelicMainProp(int depot) {
WeightedList<ReliquaryMainPropData> depotList = relicMainPropDepot.get(depot);
if (depotList == null) {
return null;
}
return depotList.next();
}
public static List<ReliquaryAffixData> getRandomRelicAffixList(int depot) {
return relicAffixDepot.get(depot);
}
}
package emu.grasscutter.data;
public abstract class GenshinResource {
public int getId() {
return 0;
}
public void onLoad() {
}
}
package emu.grasscutter.data;
import java.io.File;
import java.io.FileReader;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.Map.Entry;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Stream;
import org.reflections.Reflections;
import com.google.gson.reflect.TypeToken;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.data.custom.AbilityEmbryoEntry;
import emu.grasscutter.data.custom.OpenConfigEntry;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
public class ResourceLoader {
public static List<Class<?>> getResourceDefClasses() {
Reflections reflections = new Reflections(ResourceLoader.class.getPackage().getName());
Set<?> classes = reflections.getSubTypesOf(GenshinResource.class);
List<Class<?>> classList = new ArrayList<>(classes.size());
classes.forEach(o -> {
Class<?> c = (Class<?>) o;
if (c.getAnnotation(ResourceType.class) != null) {
classList.add(c);
}
});
classList.sort((a, b) -> {
return b.getAnnotation(ResourceType.class).loadPriority().value() - a.getAnnotation(ResourceType.class).loadPriority().value();
});
return classList;
}
public static void loadAll() {
// Create resource folder if it doesnt exist
File resFolder = new File(Grasscutter.getConfig().RESOURCE_FOLDER);
if (!resFolder.exists()) {
resFolder.mkdir();
}
// Load ability lists
loadAbilityEmbryos();
loadOpenConfig();
// Load resources
loadResources();
// Process into depots
GenshinDepot.load();
// Custom - TODO move this somewhere else
try {
GenshinData.getAvatarSkillDepotDataMap().get(504).setAbilities(
new AbilityEmbryoEntry(
"",
new String[] {
"Avatar_PlayerBoy_ExtraAttack_Wind",
"Avatar_Player_UziExplode_Mix",
"Avatar_Player_UziExplode",
"Avatar_Player_UziExplode_Strike_01",
"Avatar_Player_UziExplode_Strike_02",
"Avatar_Player_WindBreathe",
"Avatar_Player_WindBreathe_CameraController"
}
));
GenshinData.getAvatarSkillDepotDataMap().get(704).setAbilities(
new AbilityEmbryoEntry(
"",
new String[] {
"Avatar_PlayerGirl_ExtraAttack_Wind",
"Avatar_Player_UziExplode_Mix",
"Avatar_Player_UziExplode",
"Avatar_Player_UziExplode_Strike_01",
"Avatar_Player_UziExplode_Strike_02",
"Avatar_Player_WindBreathe",
"Avatar_Player_WindBreathe_CameraController"
}
));
} catch (Exception e) {
Grasscutter.getLogger().error("Error loading abilities", e);
}
}
public static void loadResources() {
for (Class<?> resourceDefinition : getResourceDefClasses()) {
ResourceType type = resourceDefinition.getAnnotation(ResourceType.class);
if (type == null) {
continue;
}
@SuppressWarnings("rawtypes")
Int2ObjectMap map = GenshinData.getMapByResourceDef(resourceDefinition);
if (map == null) {
continue;
}
try {
loadFromResource(resourceDefinition, type, map);
} catch (Exception e) {
Grasscutter.getLogger().error("Error loading resource file: " + type.name(), e);
}
}
}
@SuppressWarnings("rawtypes")
protected static void loadFromResource(Class<?> c, ResourceType type, Int2ObjectMap map) throws Exception {
for (String name : type.name()) {
loadFromResource(c, name, map);
}
Grasscutter.getLogger().info("Loaded " + map.size() + " " + c.getSimpleName() + "s.");
}
@SuppressWarnings({"rawtypes", "unchecked"})
protected static void loadFromResource(Class<?> c, String fileName, Int2ObjectMap map) throws Exception {
try (FileReader fileReader = new FileReader(Grasscutter.getConfig().RESOURCE_FOLDER + "ExcelBinOutput/" + fileName)) {
List list = Grasscutter.getGsonFactory().fromJson(fileReader, TypeToken.getParameterized(Collection.class, c).getType());
for (Object o : list) {
GenshinResource res = (GenshinResource) o;
res.onLoad();
map.put(res.getId(), res);
}
}
}
private static void loadAbilityEmbryos() {
// Read from cached file if exists
File embryoCache = new File(Grasscutter.getConfig().DATA_FOLDER + "AbilityEmbryos.json");
List<AbilityEmbryoEntry> embryoList = null;
if (embryoCache.exists()) {
// Load from cache
try (FileReader fileReader = new FileReader(embryoCache)) {
embryoList = Grasscutter.getGsonFactory().fromJson(fileReader, TypeToken.getParameterized(Collection.class, AbilityEmbryoEntry.class).getType());
} catch (Exception e) {
e.printStackTrace();
}
} else {
// Load from BinOutput
Pattern pattern = Pattern.compile("(?<=ConfigAvatar_)(.*?)(?=.json)");
embryoList = new LinkedList<>();
File folder = new File(Grasscutter.getConfig().RESOURCE_FOLDER + "BinOutput\\Avatar\\");
for (File file : folder.listFiles()) {
AvatarConfig config = null;
String avatarName = null;
Matcher matcher = pattern.matcher(file.getName());
if (matcher.find()) {
avatarName = matcher.group(0);
} else {
continue;
}
try (FileReader fileReader = new FileReader(file)) {
config = Grasscutter.getGsonFactory().fromJson(fileReader, AvatarConfig.class);
} catch (Exception e) {
e.printStackTrace();
continue;
}
if (config.abilities == null) {
continue;
}
int s = config.abilities.size();
AbilityEmbryoEntry al = new AbilityEmbryoEntry(avatarName, config.abilities.stream().map(Object::toString).toArray(size -> new String[s]));
embryoList.add(al);
}
}
if (embryoList == null || embryoList.isEmpty()) {
Grasscutter.getLogger().error("No embryos loaded!");
return;
}
for (AbilityEmbryoEntry entry : embryoList) {
GenshinData.getAbilityEmbryoInfo().put(entry.getName(), entry);
}
}
private static void loadOpenConfig() {
// Read from cached file if exists
File openConfigCache = new File(Grasscutter.getConfig().DATA_FOLDER + "OpenConfig.json");
List<OpenConfigEntry> list = null;
if (openConfigCache.exists()) {
try (FileReader fileReader = new FileReader(openConfigCache)) {
list = Grasscutter.getGsonFactory().fromJson(fileReader, TypeToken.getParameterized(Collection.class, OpenConfigEntry.class).getType());
} catch (Exception e) {
e.printStackTrace();
}
} else {
Map<String, OpenConfigEntry> map = new TreeMap<>();
java.lang.reflect.Type type = new TypeToken<Map<String, OpenConfigData[]>>() {}.getType();
String[] folderNames = {"BinOutput\\Talent\\EquipTalents\\", "BinOutput\\Talent\\AvatarTalents\\"};
for (String name : folderNames) {
File folder = new File(Grasscutter.getConfig().RESOURCE_FOLDER + name);
for (File file : folder.listFiles()) {
if (!file.getName().endsWith(".json")) {
continue;
}
Map<String, OpenConfigData[]> config = null;
try (FileReader fileReader = new FileReader(file)) {
config = Grasscutter.getGsonFactory().fromJson(fileReader, type);
} catch (Exception e) {
e.printStackTrace();
continue;
}
for (Entry<String, OpenConfigData[]> e : config.entrySet()) {
List<String> abilityList = new ArrayList<>();
int extraTalentIndex = 0;
for (OpenConfigData entry : e.getValue()) {
if (entry.$type.contains("AddAbility")) {
abilityList.add(entry.abilityName);
} else if (entry.talentIndex > 0) {
extraTalentIndex = entry.talentIndex;
}
}
OpenConfigEntry entry = new OpenConfigEntry(e.getKey(), abilityList, extraTalentIndex);
map.put(entry.getName(), entry);
}
}
}
list = new ArrayList<>(map.values());
}
if (list == null || list.isEmpty()) {
Grasscutter.getLogger().error("No openconfig entries loaded!");
return;
}
for (OpenConfigEntry entry : list) {
GenshinData.getOpenConfigEntries().put(entry.getName(), entry);
}
}
// BinOutput configs
private static class AvatarConfig {
public ArrayList<AvatarConfigAbility> abilities;
private static class AvatarConfigAbility {
public String abilityName;
public String toString() {
return abilityName;
}
}
}
private static class OpenConfig {
public OpenConfigData[] data;
}
private static class OpenConfigData {
public String $type;
public String abilityName;
public int talentIndex;
}
}
package emu.grasscutter.data;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@Retention(RetentionPolicy.RUNTIME)
public @interface ResourceType {
/** Names of the file that this Resource loads from */
String[] name();
/** Load priority - dictates which order to load this resource, with "highest" being loaded first */
LoadPriority loadPriority() default LoadPriority.NORMAL;
public enum LoadPriority {
HIGHEST (4),
HIGH (3),
NORMAL (2),
LOW (1),
LOWEST (0);
private final int value;
LoadPriority(int value) {
this.value = value;
}
public int value() {
return value;
}
}
}
package emu.grasscutter.data.common;
public class CurveInfo {
private String Type;
private String Arith;
private float Value;
public String getType() {
return Type;
}
public String getArith() {
return Arith;
}
public float getValue() {
return Value;
}
}
package emu.grasscutter.data.common;
import emu.grasscutter.game.props.FightProperty;
public class FightPropData {
private String PropType;
private FightProperty prop;
private float Value;
public String getPropType() {
return PropType;
}
public float getValue() {
return Value;
}
public FightProperty getProp() {
return prop;
}
public void onLoad() {
this.prop = FightProperty.getPropByName(PropType);
}
}
\ No newline at end of file
package emu.grasscutter.data.common;
public class ItemParamData {
private int Id;
private int Count;
public int getId() {
return Id;
}
public int getCount() {
return Count;
}
}
package emu.grasscutter.data.common;
public class PropGrowCurve {
private String Type;
private String GrowCurve;
public String getType(){
return this.Type;
}
public String getGrowCurve(){
return this.GrowCurve;
}
}
package emu.grasscutter.data.custom;
public class AbilityEmbryoEntry {
private String name;
private String[] abilities;
public AbilityEmbryoEntry() {
}
public AbilityEmbryoEntry(String avatarName, String[] array) {
this.name = avatarName;
this.abilities = array;
}
public String getName() {
return name;
}
public String[] getAbilities() {
return abilities;
}
}
package emu.grasscutter.data.custom;
import java.util.List;
public class OpenConfigEntry {
private String name;
private String[] addAbilities;
private int extraTalentIndex;
public OpenConfigEntry(String name, List<String> abilityList, int extraTalentIndex) {
this.name = name;
this.extraTalentIndex = extraTalentIndex;
if (abilityList.size() > 0) {
this.addAbilities = abilityList.toArray(new String[0]);
}
}
public String getName() {
return name;
}
public String[] getAddAbilities() {
return addAbilities;
}
public int getExtraTalentIndex() {
return extraTalentIndex;
}
}
package emu.grasscutter.data.def;
import emu.grasscutter.data.GenshinData;
import emu.grasscutter.data.GenshinResource;
import emu.grasscutter.data.ResourceType;
@ResourceType(name = "AvatarCostumeExcelConfigData.json")
public class AvatarCostumeData extends GenshinResource {
private int CostumeId;
private int ItemId;
private int AvatarId;
@Override
public int getId() {
return this.CostumeId;
}
public int getItemId() {
return this.ItemId;
}
public int getAvatarId() {
return AvatarId;
}
@Override
public void onLoad() {
GenshinData.getAvatarCostumeDataItemIdMap().put(this.getItemId(), this);
}
}
package emu.grasscutter.data.def;
import java.util.HashMap;
import java.util.Map;
import java.util.stream.Stream;
import emu.grasscutter.data.GenshinResource;
import emu.grasscutter.data.ResourceType;
import emu.grasscutter.data.common.CurveInfo;
@ResourceType(name = "AvatarCurveExcelConfigData.json")
public class AvatarCurveData extends GenshinResource {
private int Level;
private CurveInfo[] CurveInfos;
private Map<String, Float> curveInfos;
@Override
public int getId() {
return this.Level;
}
public int getLevel() {
return Level;
}
public Map<String, Float> getCurveInfos() {
return curveInfos;
}
@Override
public void onLoad() {
this.curveInfos = new HashMap<>();
Stream.of(this.CurveInfos).forEach(info -> this.curveInfos.put(info.getType(), info.getValue()));
}
}
package emu.grasscutter.data.def;
import java.util.List;
import emu.grasscutter.data.GenshinData;
import emu.grasscutter.data.GenshinResource;
import emu.grasscutter.data.ResourceType;
import emu.grasscutter.data.ResourceType.LoadPriority;
import emu.grasscutter.data.common.PropGrowCurve;
import emu.grasscutter.data.custom.AbilityEmbryoEntry;
import emu.grasscutter.game.props.FightProperty;
import emu.grasscutter.utils.Utils;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.IntArrayList;
import it.unimi.dsi.fastutil.ints.IntList;
@ResourceType(name = "AvatarExcelConfigData.json", loadPriority = LoadPriority.LOW)
public class AvatarData extends GenshinResource {
private String name;
private String IconName;
private String BodyType;
private String QualityType;
private int ChargeEfficiency;
private int InitialWeapon;
private String WeaponType;
private String ImageName;
private int AvatarPromoteId;
private String CutsceneShow;
private int SkillDepotId;
private int StaminaRecoverSpeed;
private List<String> CandSkillDepotIds;
private long DescTextMapHash;
private String AvatarIdentityType;
private List<Integer> AvatarPromoteRewardLevelList;
private List<Integer> AvatarPromoteRewardIdList;
private int FeatureTagGroupID;
private long NameTextMapHash;
private long GachaImageNameHashSuffix;
private long InfoDescTextMapHash;
private float HpBase;
private float AttackBase;
private float DefenseBase;
private float Critical;
private float CriticalHurt;
private List<PropGrowCurve> PropGrowCurves;
private int Id;
private Int2ObjectMap<String> growthCurveMap;
private float[] hpGrowthCurve;
private float[] attackGrowthCurve;
private float[] defenseGrowthCurve;
private AvatarSkillDepotData skillDepot;
private IntList abilities;
@Override
public int getId(){
return this.Id;
}
public String getName() {
return name;
}
public String getBodyType(){
return this.BodyType;
}
public String getQualityType(){
return this.QualityType;
}
public int getChargeEfficiency(){
return this.ChargeEfficiency;
}
public int getInitialWeapon(){
return this.InitialWeapon;
}
public String getWeaponType(){
return this.WeaponType;
}
public String getImageName(){
return this.ImageName;
}
public int getAvatarPromoteId(){
return this.AvatarPromoteId;
}
public long getGachaImageNameHashSuffix(){
return this.GachaImageNameHashSuffix;
}
public String getCutsceneShow(){
return this.CutsceneShow;
}
public int getSkillDepotId(){
return this.SkillDepotId;
}
public int getStaminaRecoverSpeed(){
return this.StaminaRecoverSpeed;
}
public List<String> getCandSkillDepotIds(){
return this.CandSkillDepotIds;
}
public long getDescTextMapHash(){
return this.DescTextMapHash;
}
public String getAvatarIdentityType(){
return this.AvatarIdentityType;
}
public List<Integer> getAvatarPromoteRewardLevelList(){
return this.AvatarPromoteRewardLevelList;
}
public List<Integer> getAvatarPromoteRewardIdList(){
return this.AvatarPromoteRewardIdList;
}
public int getFeatureTagGroupID(){
return this.FeatureTagGroupID;
}
public long getInfoDescTextMapHash(){
return this.InfoDescTextMapHash;
}
public float getBaseHp(int level){
try {
return this.HpBase * this.hpGrowthCurve[level - 1];
} catch (Exception e) {
return this.HpBase;
}
}
public float getBaseAttack(int level){
try {
return this.AttackBase * this.attackGrowthCurve[level - 1];
} catch (Exception e) {
return this.AttackBase;
}
}
public float getBaseDefense(int level){
try {
return this.DefenseBase * this.defenseGrowthCurve[level - 1];
} catch (Exception e) {
return this.DefenseBase;
}
}
public float getBaseCritical(){
return this.Critical;
}
public float getBaseCriticalHurt(){
return this.CriticalHurt;
}
public float getGrowthCurveById(int level, FightProperty prop) {
String growCurve = this.growthCurveMap.get(prop.getId());
if (growCurve == null) {
return 1f;
}
AvatarCurveData curveData = GenshinData.getAvatarCurveDataMap().get(level);
if (curveData == null) {
return 1f;
}
return curveData.getCurveInfos().getOrDefault(growCurve, 1f);
}
public long getNameTextMapHash(){
return this.NameTextMapHash;
}
public AvatarSkillDepotData getSkillDepot() {
return skillDepot;
}
public IntList getAbilities() {
return abilities;
}
@Override
public void onLoad() {
this.skillDepot = GenshinData.getAvatarSkillDepotDataMap().get(this.SkillDepotId);
int size = GenshinData.getAvatarCurveDataMap().size();
this.hpGrowthCurve = new float[size];
this.attackGrowthCurve = new float[size];
this.defenseGrowthCurve = new float[size];
for (AvatarCurveData curveData : GenshinData.getAvatarCurveDataMap().values()) {
int level = curveData.getLevel() - 1;
for (PropGrowCurve growCurve : this.PropGrowCurves) {
FightProperty prop = FightProperty.getPropByName(growCurve.getType());
switch (prop) {
case FIGHT_PROP_BASE_HP:
this.hpGrowthCurve[level] = curveData.getCurveInfos().get(growCurve.getGrowCurve());
break;
case FIGHT_PROP_BASE_ATTACK:
this.attackGrowthCurve[level] = curveData.getCurveInfos().get(growCurve.getGrowCurve());
break;
case FIGHT_PROP_BASE_DEFENSE:
this.defenseGrowthCurve[level] = curveData.getCurveInfos().get(growCurve.getGrowCurve());
break;
default:
break;
}
}
}
/*
for (PropGrowCurve growCurve : this.PropGrowCurves) {
FightProperty prop = FightProperty.getPropByName(growCurve.getType());
this.growthCurveMap.put(prop.getId(), growCurve.getGrowCurve());
}
*/
// Cache abilities
String[] split = this.IconName.split("_");
if (split.length > 0) {
this.name = split[split.length - 1];
AbilityEmbryoEntry info = GenshinData.getAbilityEmbryoInfo().get(this.name);
if (info != null) {
this.abilities = new IntArrayList(info.getAbilities().length);
for (String ability : info.getAbilities()) {
this.abilities.add(Utils.abilityHash(ability));
}
}
}
}
}
package emu.grasscutter.data.def;
import emu.grasscutter.data.GenshinResource;
import emu.grasscutter.data.ResourceType;
@ResourceType(name = "AvatarFlycloakExcelConfigData.json")
public class AvatarFlycloakData extends GenshinResource {
private int FlycloakId;
private long NameTextMapHash;
@Override
public int getId() {
return this.FlycloakId;
}
public long getNameTextMapHash() {
return NameTextMapHash;
}
@Override
public void onLoad() {
}
}
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