Commit 716380a0 authored by Melledy's avatar Melledy
Browse files

Merge branch 'development' into java-16

parents 09c8ed34 627d3dda
name: "Build"
on:
push:
branches:
- "stable"
jobs:
Build-Server-Jar:
runs-on: windows-latest
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Setup Java
uses: actions/setup-java@v3
with:
distribution: temurin
java-version: '8'
- name: Run Gradle
run: .\gradlew.bat && .\gradlew jar
- name: Upload build
uses: actions/upload-artifact@v3
with:
name: Grasscutter
path: grasscutter.jar
...@@ -47,12 +47,13 @@ tmp/ ...@@ -47,12 +47,13 @@ tmp/
# Grasscutter # Grasscutter
resources/* resources/*
logs/*
data/AbilityEmbryos.json data/AbilityEmbryos.json
data/OpenConfig.json data/OpenConfig.json
proto/auto/ proto/auto/
proto/protoc.exe proto/protoc.exe
GM Handbook.txt GM Handbook.txt
config.json config.json
mitmdump.exe mitmdump.exe
grasscutter.jar grasscutter.jar
mongod.exe mongod.exe
...@@ -12,6 +12,9 @@ A WIP server reimplementation for *some anime game* 2.3-2.6 ...@@ -12,6 +12,9 @@ A WIP server reimplementation for *some anime game* 2.3-2.6
* Friends list * Friends list
* Co-op *partially* work * Co-op *partially* work
# Quick setup guide # Quick setup guide
### Note
* If you update from an older version, delete `config.json` for regeneration
### Prerequisites ### Prerequisites
* JDK-8u202 ([mirror link](https://mirrors.huaweicloud.com/java/jdk/8u202-b08/) since Oracle required an account to download old builds) * JDK-8u202 ([mirror link](https://mirrors.huaweicloud.com/java/jdk/8u202-b08/) since Oracle required an account to download old builds)
* Mongodb (recommended 4.0+) * Mongodb (recommended 4.0+)
...@@ -26,7 +29,7 @@ A WIP server reimplementation for *some anime game* 2.3-2.6 ...@@ -26,7 +29,7 @@ A WIP server reimplementation for *some anime game* 2.3-2.6
### Connecting with the client ### Connecting with the client
½. Create an account using *server console command* below ½. Create an account using *server console command* below
1. Run a proxy daemon: (choose either one) 1. Run a proxy daemon: (choose either one)
- mitmdump: `mitmdump -s proxy.py --ssl-insecure` - mitmdump: `mitmdump -s proxy.py -k`
- Fiddler Classic: Run Fiddler Classic, turn on `Decrypt https traffic` in setting and change the default port there (Tools -> Options -> Connections) to anything other than `8888`, and load [this script](https://github.lunatic.moe/fiddlerscript). - Fiddler Classic: Run Fiddler Classic, turn on `Decrypt https traffic` in setting and change the default port there (Tools -> Options -> Connections) to anything other than `8888`, and load [this script](https://github.lunatic.moe/fiddlerscript).
- [Hosts file](https://github.com/Melledy/Grasscutter/wiki/Running#traffic-route-map) - [Hosts file](https://github.com/Melledy/Grasscutter/wiki/Running#traffic-route-map)
2. Trust CA certificate: 2. Trust CA certificate:
...@@ -37,32 +40,33 @@ A WIP server reimplementation for *some anime game* 2.3-2.6 ...@@ -37,32 +40,33 @@ A WIP server reimplementation for *some anime game* 2.3-2.6
* or you can use `run.cmd` to start Server & Proxy daemon with one click * or you can use `run.cmd` to start Server & Proxy daemon with one click
# Grasscutter commands # Grasscutter commands
### Server console commands There is a dummy user named "Server" in every player's friends list that you can message to use commands. Commands also work in other chat rooms, such as private/team chats.
`account create [username] {playerid}` - Creates an account with the specified username and the in-game uid for that account. The playerid parameter is optional and will be auto generated if not set. `account create [username] {playerid}` - Creates an account with the specified username and the in-game uid for that account. The playerid parameter is optional and will be auto generated if not set.
### In-Game commands `spawn [monster id] [level] [amount]`
There is a dummy user named "Server" in every player's friends list that you can message to use commands. Commands also work in other chat rooms, such as private/team chats.
`give [item id] [amount]`
`!spawn [monster id] [level] [amount]` `givechar [avatar id] [level]`
`!give [item id] [amount]` `drop [item id] [amount]`
`!givechar [avatar id] [level]` `killall`
`!drop [item id] [amount]` `setworldlevel [level]` - Relog to see effects properly
`!killall` `godmode` - Prevents you from taking damage
`!setworldlevel [level]` - Relog to see effects properly `resetconst` - Resets the constellation level on your current active character, will need to relog after using the command to see any changes.
`!godmode` - Prevents you from taking damage `setstats [stats] [amount]` - Changes the current character's specified stat.
`!resetconst` - Resets the constellation level on your current active character, will need to relog after using the command to see any changes. `clearartifacts` - Deletes all unequipped and unlocked level 0 artifacts, **including yellow rarity ones** from your inventory
`!sethp [hp]` `pos` - Gets your current coordinate.
`!clearartifacts` - Deletes all unequipped and unlocked level 0 artifacts, **including yellow rarity ones** from your inventory `weather [weather id] [climate id]` - Changes the current weather.
*More commands will be updated in the [wiki](https://github.com/Melledy/Grasscutter/wiki/).* *More commands will be updated in the [wiki](https://github.com/Melledy/Grasscutter/wiki/).*
......
...@@ -19,7 +19,7 @@ ...@@ -19,7 +19,7 @@
"bannerType": "EVENT", "bannerType": "EVENT",
"prefabPath": "GachaShowPanel_A079", "prefabPath": "GachaShowPanel_A079",
"previewPrefabPath": "UI_Tab_GachaShowPanel_A079", "previewPrefabPath": "UI_Tab_GachaShowPanel_A079",
"titlePath": "UI_GACHA_SHOW_PANEL_A079_TITLE", "titlePath": "UI_GACHA_SHOW_PANEL_A048_TITLE",
"costItem": 223, "costItem": 223,
"beginTime": 0, "beginTime": 0,
"endTime": 1924992000, "endTime": 1924992000,
...@@ -34,7 +34,7 @@ ...@@ -34,7 +34,7 @@
"bannerType": "WEAPON", "bannerType": "WEAPON",
"prefabPath": "GachaShowPanel_A080", "prefabPath": "GachaShowPanel_A080",
"previewPrefabPath": "UI_Tab_GachaShowPanel_A080", "previewPrefabPath": "UI_Tab_GachaShowPanel_A080",
"titlePath": "UI_GACHA_SHOW_PANEL_A080_TITLE", "titlePath": "UI_GACHA_SHOW_PANEL_A021_TITLE",
"costItem": 223, "costItem": 223,
"beginTime": 0, "beginTime": 0,
"endTime": 1924992000, "endTime": 1924992000,
......
...@@ -16,12 +16,14 @@ ...@@ -16,12 +16,14 @@
# - mitmdump from mitmproxy # - mitmdump from mitmproxy
# #
# @author MlgmXyysd # @author MlgmXyysd
# @version 1.0 # @version 1.1
# #
## ##
from mitmproxy import http from mitmproxy import http
from proxy_config import USE_SSL
from proxy_config import REMOTE_HOST from proxy_config import REMOTE_HOST
from proxy_config import REMOTE_PORT
class MlgmXyysd_Genshin_Impact_Proxy: class MlgmXyysd_Genshin_Impact_Proxy:
...@@ -55,12 +57,19 @@ class MlgmXyysd_Genshin_Impact_Proxy: ...@@ -55,12 +57,19 @@ class MlgmXyysd_Genshin_Impact_Proxy:
"minor-api.mihoyo.com", "minor-api.mihoyo.com",
"public-data-api.mihoyo.com", "public-data-api.mihoyo.com",
"uspider.yuanshen.com", "uspider.yuanshen.com",
"sdk-static.mihoyo.com" "sdk-static.mihoyo.com",
"abtest-api-data-sg.hoyoverse.com",
"log-upload-os.hoyoverse.com"
] ]
def request(self, flow: http.HTTPFlow) -> None: def request(self, flow: http.HTTPFlow) -> None:
if flow.request.host in self.LIST_DOMAINS: if flow.request.host in self.LIST_DOMAINS:
if USE_SSL:
flow.request.scheme = "https"
else:
flow.request.scheme = "http"
flow.request.host = REMOTE_HOST flow.request.host = REMOTE_HOST
flow.request.port = REMOTE_PORT
addons = [ addons = [
MlgmXyysd_Genshin_Impact_Proxy() MlgmXyysd_Genshin_Impact_Proxy()
......
# This can also be replaced with another IP address. # This can also be replaced with another IP address.
REMOTE_HOST = "localhost" USE_SSL = True
\ No newline at end of file REMOTE_HOST = "127.0.0.1"
REMOTE_PORT = 443
\ No newline at end of file
package emu.grasscutter; package emu.grasscutter;
import java.util.ArrayList;
public final class Config { public final class Config {
public String DispatchServerIp = "127.0.0.1";
public String DispatchServerPublicIp = "";
public int DispatchServerPort = 443;
public String DispatchServerKeystorePath = "./keystore.p12";
public String DispatchServerKeystorePassword = "";
public Boolean UseSSL = true;
public String GameServerName = "Test";
public String GameServerIp = "127.0.0.1";
public String GameServerPublicIp = "";
public int GameServerPort = 22102;
public int UploadLogPort = 80;
public String DatabaseUrl = "mongodb://localhost:27017"; public String DatabaseUrl = "mongodb://localhost:27017";
public String DatabaseCollection = "grasscutter"; public String DatabaseCollection = "grasscutter";
public String RESOURCE_FOLDER = "./resources/"; public String RESOURCE_FOLDER = "./resources/";
public String DATA_FOLDER = "./data/"; public String DATA_FOLDER = "./data/";
public String PACKETS_FOLDER = "./packets/"; public String PACKETS_FOLDER = "./packets/";
public String DUMPS_FOLDER = "./dumps/"; public String DUMPS_FOLDER = "./dumps/";
public String KEY_FOLDER = "./keys/"; public String KEY_FOLDER = "./keys/";
public boolean LOG_PACKETS = false;
public GameRates Game = new GameRates(); public String RunMode = "HYBRID"; // HYBRID, DISPATCH_ONLY, GAME_ONLY
public ServerOptions ServerOptions = new ServerOptions(); public GameServerOptions GameServer = new GameServerOptions();
public DispatchServerOptions DispatchServer = new DispatchServerOptions();
public GameRates getGameRates() {
return Game; public GameServerOptions getGameServerOptions() {
} return GameServer;
public ServerOptions getServerOptions() {
return ServerOptions;
} }
public static class GameRates { public DispatchServerOptions getDispatchOptions() { return DispatchServer; }
public float ADVENTURE_EXP_RATE = 1.0f;
public float MORA_RATE = 1.0f; public static class DispatchServerOptions {
public float DOMAIN_DROP_RATE = 1.0f; public String Ip = "0.0.0.0";
public String PublicIp = "127.0.0.1";
public int Port = 443;
public int PublicPort = 0;
public String KeystorePath = "./keystore.p12";
public String KeystorePassword = "";
public Boolean UseSSL = true;
public Boolean FrontHTTPS = true;
public boolean AutomaticallyCreateAccounts = false;
public RegionInfo[] GameServers = {};
public RegionInfo[] getGameServers() {
return GameServers;
}
public static class RegionInfo {
public String Name = "os_usa";
public String Title = "Test";
public String Ip = "127.0.0.1";
public int Port = 22102;
}
} }
public static class ServerOptions { public static class GameServerOptions {
public String Name = "Test";
public String Ip = "0.0.0.0";
public String PublicIp = "127.0.0.1";
public int Port = 22102;
public int PublicPort = 0;
public String DispatchServerDatabaseUrl = "mongodb://localhost:27017";
public String DispatchServerDatabaseCollection = "grasscutter";
public boolean LOG_PACKETS = false;
public int InventoryLimitWeapon = 2000; public int InventoryLimitWeapon = 2000;
public int InventoryLimitRelic = 2000; public int InventoryLimitRelic = 2000;
public int InventoryLimitMaterial = 2000; public int InventoryLimitMaterial = 2000;
...@@ -54,6 +72,15 @@ public final class Config { ...@@ -54,6 +72,15 @@ public final class Config {
public boolean WatchGacha = false; public boolean WatchGacha = false;
public int[] WelcomeEmotes = {2007, 1002, 4010}; public int[] WelcomeEmotes = {2007, 1002, 4010};
public String WelcomeMotd = "Welcome to Grasscutter emu"; public String WelcomeMotd = "Welcome to Grasscutter emu";
public boolean AutomaticallyCreateAccounts = false;
public GameRates Game = new GameRates();
public GameRates getGameRates() { return Game; }
public static class GameRates {
public float ADVENTURE_EXP_RATE = 1.0f;
public float MORA_RATE = 1.0f;
public float DOMAIN_DROP_RATE = 1.0f;
}
} }
} }
...@@ -73,11 +73,26 @@ public final class Grasscutter { ...@@ -73,11 +73,26 @@ public final class Grasscutter {
DatabaseManager.initialize(); DatabaseManager.initialize();
// Start servers. // Start servers.
dispatchServer = new DispatchServer(); if(getConfig().RunMode.equalsIgnoreCase("HYBRID")) {
dispatchServer.start(); dispatchServer = new DispatchServer();
dispatchServer.start();
gameServer = new GameServer(new InetSocketAddress(getConfig().GameServerIp, getConfig().GameServerPort));
gameServer.start(); gameServer = new GameServer(new InetSocketAddress(getConfig().getGameServerOptions().Ip, getConfig().getGameServerOptions().Port));
gameServer.start();
} else if(getConfig().RunMode.equalsIgnoreCase("DISPATCH_ONLY")) {
dispatchServer = new DispatchServer();
dispatchServer.start();
} else if(getConfig().RunMode.equalsIgnoreCase("GAME_ONLY")) {
gameServer = new GameServer(new InetSocketAddress(getConfig().getGameServerOptions().Ip, getConfig().getGameServerOptions().Port));
gameServer.start();
} else {
getLogger().error("Invalid server run mode. " + getConfig().RunMode);
getLogger().error("Server run mode must be 'HYBRID', 'DISPATCH_ONLY', or 'GAME_ONLY'. Unable to start Grasscutter...");
getLogger().error("Shutting down...");
System.exit(1);
}
// Open console. // Open console.
startConsole(); startConsole();
...@@ -86,8 +101,10 @@ public final class Grasscutter { ...@@ -86,8 +101,10 @@ public final class Grasscutter {
public static void loadConfig() { public static void loadConfig() {
try (FileReader file = new FileReader(configFile)) { try (FileReader file = new FileReader(configFile)) {
config = gson.fromJson(file, Config.class); config = gson.fromJson(file, Config.class);
saveConfig();
} catch (Exception e) { } catch (Exception e) {
Grasscutter.config = new Config(); saveConfig(); Grasscutter.config = new Config();
saveConfig();
} }
} }
...@@ -104,9 +121,14 @@ public final class Grasscutter { ...@@ -104,9 +121,14 @@ public final class Grasscutter {
try (BufferedReader br = new BufferedReader(new InputStreamReader(System.in))) { try (BufferedReader br = new BufferedReader(new InputStreamReader(System.in))) {
while ((input = br.readLine()) != null) { while ((input = br.readLine()) != null) {
try { try {
if(getConfig().RunMode.equalsIgnoreCase("DISPATCH_ONLY")) {
getLogger().error("Commands are not supported in dispatch only mode");
return;
}
CommandMap.getInstance().invoke(null, input); CommandMap.getInstance().invoke(null, input);
} catch (Exception e) { } catch (Exception e) {
Grasscutter.getLogger().error("Command error: " + e.getMessage()); Grasscutter.getLogger().error("Command error: ");
e.printStackTrace();
} }
} }
} catch (Exception e) { } catch (Exception e) {
......
...@@ -46,7 +46,7 @@ public final class AccountCommand implements CommandHandler { ...@@ -46,7 +46,7 @@ public final class AccountCommand implements CommandHandler {
CommandHandler.sendMessage(null, "Account already exists."); CommandHandler.sendMessage(null, "Account already exists.");
return; return;
} else { } else {
CommandHandler.sendMessage(null, "Account created with UID " + account.getPlayerId() + "."); CommandHandler.sendMessage(null, "Account created with UID " + account.getPlayerUid() + ".");
account.addPermission("*"); // Grant the player superuser permissions. account.addPermission("*"); // Grant the player superuser permissions.
account.save(); // Save account to database. account.save(); // Save account to database.
} }
......
...@@ -23,11 +23,17 @@ public final class ChangeSceneCommand implements CommandHandler { ...@@ -23,11 +23,17 @@ public final class ChangeSceneCommand implements CommandHandler {
try { try {
int sceneId = Integer.parseInt(args.get(0)); int sceneId = Integer.parseInt(args.get(0));
if (sceneId == sender.getSceneId()) {
CommandHandler.sendMessage(sender, "You are already in that scene");
return;
}
boolean result = sender.getWorld().transferPlayerToScene(sender, sceneId, sender.getPos()); boolean result = sender.getWorld().transferPlayerToScene(sender, sceneId, sender.getPos());
CommandHandler.sendMessage(sender, "Changed to scene " + sceneId); CommandHandler.sendMessage(sender, "Changed to scene " + sceneId);
if (!result) { if (!result) {
CommandHandler.sendMessage(sender, "Scene does not exist or you are already in it"); CommandHandler.sendMessage(sender, "Scene does not exist");
} }
} catch (Exception e) { } catch (Exception e) {
CommandHandler.sendMessage(sender, "Usage: changescene <scene id>"); CommandHandler.sendMessage(sender, "Usage: changescene <scene id>");
......
...@@ -95,18 +95,19 @@ public final class GiveCommand implements CommandHandler { ...@@ -95,18 +95,19 @@ public final class GiveCommand implements CommandHandler {
} }
private void item(GenshinPlayer player, ItemData itemData, int amount) { private void item(GenshinPlayer player, ItemData itemData, int amount) {
GenshinItem genshinItem = new GenshinItem(itemData);
if (itemData.isEquip()) { if (itemData.isEquip()) {
List<GenshinItem> items = new LinkedList<>(); List<GenshinItem> items = new LinkedList<>();
for (int i = 0; i < amount; i++) { for (int i = 0; i < amount; i++) {
items.add(genshinItem); items.add(new GenshinItem(itemData));
} }
player.getInventory().addItems(items); player.getInventory().addItems(items);
player.sendPacket(new PacketItemAddHintNotify(items, ActionReason.SubfieldDrop)); player.sendPacket(new PacketItemAddHintNotify(items, ActionReason.SubfieldDrop));
} else { } else {
GenshinItem genshinItem = new GenshinItem(itemData);
genshinItem.setCount(amount); genshinItem.setCount(amount);
player.getInventory().addItem(genshinItem); player.getInventory().addItem(genshinItem);
player.sendPacket(new PacketItemAddHintNotify(genshinItem, ActionReason.SubfieldDrop)); player.sendPacket(new PacketItemAddHintNotify(genshinItem, ActionReason.SubfieldDrop));
} }
} }
} }
package emu.grasscutter.command.commands;
import emu.grasscutter.command.Command;
import emu.grasscutter.command.CommandHandler;
import emu.grasscutter.game.GenshinPlayer;
import emu.grasscutter.game.props.FightProperty;
import emu.grasscutter.server.packet.send.PacketAvatarFightPropUpdateNotify;
import emu.grasscutter.server.packet.send.PacketAvatarLifeStateChangeNotify;
import emu.grasscutter.server.packet.send.PacketEntityFightPropUpdateNotify;
import emu.grasscutter.server.packet.send.PacketLifeStateChangeNotify;
import java.util.List;
@Command(label = "heal", usage = "heal|h",
description = "Heal all characters in your current team.", aliases = {"h"}, permission = "player.heal")
public class HealCommand implements CommandHandler {
@Override
public void execute(GenshinPlayer sender, List<String> args) {
if (sender == null) {
CommandHandler.sendMessage(null, "Run this command in-game.");
return;
}
sender.getTeamManager().getActiveTeam().forEach(entity -> {
boolean isAlive = entity.isAlive();
entity.setFightProperty(
FightProperty.FIGHT_PROP_CUR_HP,
entity.getFightProperty(FightProperty.FIGHT_PROP_MAX_HP)
);
entity.getWorld().broadcastPacket(new PacketAvatarFightPropUpdateNotify(entity.getAvatar(), FightProperty.FIGHT_PROP_CUR_HP));
if (!isAlive) {
entity.getWorld().broadcastPacket(new PacketAvatarLifeStateChangeNotify(entity.getAvatar()));
}
});
CommandHandler.sendMessage(sender, "All characters are healed.");
}
}
...@@ -22,7 +22,7 @@ public final class KickCommand implements CommandHandler { ...@@ -22,7 +22,7 @@ public final class KickCommand implements CommandHandler {
} }
if (sender != null) { if (sender != null) {
CommandHandler.sendMessage(sender, String.format("Player [%s:%s] has kicked player [%s:%s]", sender.getAccount().getPlayerId(), sender.getAccount().getUsername(), target, targetPlayer.getAccount().getUsername())); CommandHandler.sendMessage(sender, String.format("Player [%s:%s] has kicked player [%s:%s]", sender.getAccount().getPlayerUid(), sender.getAccount().getUsername(), target, targetPlayer.getAccount().getUsername()));
} }
CommandHandler.sendMessage(sender, String.format("Kicking player [%s:%s]", target, targetPlayer.getAccount().getUsername())); CommandHandler.sendMessage(sender, String.format("Kicking player [%s:%s]", target, targetPlayer.getAccount().getUsername()));
......
package emu.grasscutter.command.commands;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.command.Command;
import emu.grasscutter.command.CommandHandler;
import emu.grasscutter.game.GenshinPlayer;
import java.util.List;
import java.util.Map;
@Command(label = "list", description = "List online players")
public class ListCommand implements CommandHandler {
@Override
public void execute(GenshinPlayer sender, List<String> args) {
Map<Integer, GenshinPlayer> playersMap = Grasscutter.getGameServer().getPlayers();
CommandHandler.sendMessage(sender, String.format("There are %s player(s) online:", playersMap.size()));
if (playersMap.size() != 0) {
StringBuilder playerSet = new StringBuilder();
for (Map.Entry<Integer, GenshinPlayer> entry : playersMap.entrySet()) {
playerSet.append(entry.getValue().getNickname());
playerSet.append(", ");
}
String players = playerSet.toString();
CommandHandler.sendMessage(sender, players.substring(0, players.length() - 2));
}
}
}
...@@ -10,7 +10,7 @@ import emu.grasscutter.server.packet.send.PacketEntityFightPropUpdateNotify; ...@@ -10,7 +10,7 @@ import emu.grasscutter.server.packet.send.PacketEntityFightPropUpdateNotify;
import java.util.List; import java.util.List;
@Command(label = "setstats", usage = "setstats|stats <stat> <value>", @Command(label = "setstats", usage = "setstats|stats <stat> <value>",
aliases = {"stats"}) description = "Set fight property for your current active character", aliases = {"stats"}, permission = "player.setstats")
public final class SetStatsCommand implements CommandHandler { public final class SetStatsCommand implements CommandHandler {
@Override @Override
...@@ -20,6 +20,11 @@ public final class SetStatsCommand implements CommandHandler { ...@@ -20,6 +20,11 @@ public final class SetStatsCommand implements CommandHandler {
return; return;
} }
if (args.size() < 2){
CommandHandler.sendMessage(sender, "Usage: setstats|stats <stat> <value>");
return;
}
String stat = args.get(0); String stat = args.get(0);
switch (stat) { switch (stat) {
default: default:
......
package emu.grasscutter.command.commands;
import emu.grasscutter.command.Command;
import emu.grasscutter.command.CommandHandler;
import emu.grasscutter.game.GenshinPlayer;
import emu.grasscutter.game.avatar.GenshinAvatar;
import emu.grasscutter.game.entity.EntityAvatar;
import emu.grasscutter.server.packet.send.PacketAvatarSkillChangeNotify;
import emu.grasscutter.server.packet.send.PacketAvatarSkillUpgradeRsp;
import java.util.List;
@Command(label = "talent", usage = "talent <talentID> <value>",
description = "Set talent level for your current active character", permission = "player.settalent")
public class TalentCommand 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() < 0 || args.size() < 1){
CommandHandler.sendMessage(sender, "To set talent level: /talent set <talentID> <value>");
CommandHandler.sendMessage(sender, "To get talent ID: /talent getid");
return;
}
String cmdSwitch = args.get(0);
switch (cmdSwitch) {
default:
CommandHandler.sendMessage(sender, "To set talent level: /talent set <talentID> <value>");
CommandHandler.sendMessage(sender, "To get talent ID: /talent getid");
return;
case "set":
try {
int skillId = Integer.parseInt(args.get(1));
int nextLevel = Integer.parseInt(args.get(2));
EntityAvatar entity = sender.getTeamManager().getCurrentAvatarEntity();
GenshinAvatar avatar = entity.getAvatar();
int skillIdNorAtk = avatar.getData().getSkillDepot().getSkills().get(0);
int skillIdE = avatar.getData().getSkillDepot().getSkills().get(1);
int skillIdQ = avatar.getData().getSkillDepot().getEnergySkill();
int currentLevelNorAtk = avatar.getSkillLevelMap().get(skillIdNorAtk);
int currentLevelE = avatar.getSkillLevelMap().get(skillIdE);
int currentLevelQ = avatar.getSkillLevelMap().get(skillIdQ);
if (args.size() < 2){
CommandHandler.sendMessage(sender, "To set talent level: /talent set <talentID> <value>");
CommandHandler.sendMessage(sender, "To get talent ID: /talent getid");
return;
}
if (nextLevel > 16){
CommandHandler.sendMessage(sender, "Invalid talent level. Level should be lower than 16");
return;
}
if (skillId == skillIdNorAtk){
// Upgrade skill
avatar.getSkillLevelMap().put(skillIdNorAtk, nextLevel);
avatar.save();
// Packet
sender.sendPacket(new PacketAvatarSkillChangeNotify(avatar, skillIdNorAtk, currentLevelNorAtk, nextLevel));
sender.sendPacket(new PacketAvatarSkillUpgradeRsp(avatar, skillIdNorAtk, currentLevelNorAtk, nextLevel));
CommandHandler.sendMessage(sender, "Set talent Normal ATK to " + nextLevel + ".");
}
if (skillId == skillIdE){
// Upgrade skill
avatar.getSkillLevelMap().put(skillIdE, nextLevel);
avatar.save();
// Packet
sender.sendPacket(new PacketAvatarSkillChangeNotify(avatar, skillIdE, currentLevelE, nextLevel));
sender.sendPacket(new PacketAvatarSkillUpgradeRsp(avatar, skillIdE, currentLevelE, nextLevel));
CommandHandler.sendMessage(sender, "Set talent E to " + nextLevel + ".");
}
if (skillId == skillIdQ){
// Upgrade skill
avatar.getSkillLevelMap().put(skillIdQ, nextLevel);
avatar.save();
// Packet
sender.sendPacket(new PacketAvatarSkillChangeNotify(avatar, skillIdQ, currentLevelQ, nextLevel));
sender.sendPacket(new PacketAvatarSkillUpgradeRsp(avatar, skillIdQ, currentLevelQ, nextLevel));
CommandHandler.sendMessage(sender, "Set talent Q to " + nextLevel + ".");
}
} catch (NumberFormatException ignored) {
CommandHandler.sendMessage(sender, "Invalid skill ID.");
return;
}
break;
case "getid":
EntityAvatar entity = sender.getTeamManager().getCurrentAvatarEntity();
GenshinAvatar avatar = entity.getAvatar();
int skillIdNorAtk = avatar.getData().getSkillDepot().getSkills().get(0);
int skillIdE = avatar.getData().getSkillDepot().getSkills().get(1);
int skillIdQ = avatar.getData().getSkillDepot().getEnergySkill();
CommandHandler.sendMessage(sender, "Normal Attack ID " + skillIdNorAtk + ".");
CommandHandler.sendMessage(sender, "E skill ID " + skillIdE + ".");
CommandHandler.sendMessage(sender, "Q skill ID " + skillIdQ + ".");
break;
}
}
}
...@@ -8,7 +8,7 @@ import emu.grasscutter.server.packet.send.PacketSceneAreaWeatherNotify; ...@@ -8,7 +8,7 @@ import emu.grasscutter.server.packet.send.PacketSceneAreaWeatherNotify;
import java.util.List; import java.util.List;
@Command(label = "weather", usage = "weather <weatherId>", @Command(label = "weather", usage = "weather <weatherId> [climateId]",
description = "Changes the weather.", aliases = {"w"}, permission = "player.weather") description = "Changes the weather.", aliases = {"w"}, permission = "player.weather")
public final class WeatherCommand implements CommandHandler { public final class WeatherCommand implements CommandHandler {
...@@ -20,20 +20,22 @@ public final class WeatherCommand implements CommandHandler { ...@@ -20,20 +20,22 @@ public final class WeatherCommand implements CommandHandler {
} }
if (args.size() < 1) { if (args.size() < 1) {
CommandHandler.sendMessage(sender, "Usage: weather <weatherId>"); CommandHandler.sendMessage(sender, "Usage: weather <weatherId> [climateId]");
return; return;
} }
try { try {
int weatherId = Integer.parseInt(args.get(0)); int weatherId = Integer.parseInt(args.get(0));
int climateId = args.size() > 1 ? Integer.parseInt(args.get(1)) : 1;
ClimateType climate = ClimateType.getTypeByValue(weatherId); ClimateType climate = ClimateType.getTypeByValue(climateId);
sender.getScene().setWeather(weatherId);
sender.getScene().setClimate(climate); sender.getScene().setClimate(climate);
sender.getScene().broadcastPacket(new PacketSceneAreaWeatherNotify(sender)); sender.getScene().broadcastPacket(new PacketSceneAreaWeatherNotify(sender));
CommandHandler.sendMessage(sender, "Changed weather to " + weatherId); CommandHandler.sendMessage(sender, "Changed weather to " + weatherId + " with climate " + climateId);
} catch (NumberFormatException ignored) { } catch (NumberFormatException ignored) {
CommandHandler.sendMessage(sender, "Invalid weather ID."); CommandHandler.sendMessage(sender, "Invalid ID.");
} }
} }
} }
package emu.grasscutter.data; package emu.grasscutter.data;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.List;
import java.util.Map; import java.util.Map;
import emu.grasscutter.Grasscutter; import emu.grasscutter.Grasscutter;
...@@ -54,6 +56,10 @@ public class GenshinData { ...@@ -54,6 +56,10 @@ public class GenshinData {
private static final Int2ObjectMap<AvatarCostumeData> avatarCostumeDataItemIdMap = new Int2ObjectLinkedOpenHashMap<>(); private static final Int2ObjectMap<AvatarCostumeData> avatarCostumeDataItemIdMap = new Int2ObjectLinkedOpenHashMap<>();
private static final Int2ObjectMap<SceneData> sceneDataMap = new Int2ObjectLinkedOpenHashMap<>(); private static final Int2ObjectMap<SceneData> sceneDataMap = new Int2ObjectLinkedOpenHashMap<>();
private static final Int2ObjectMap<FetterData> fetterDataMap = new Int2ObjectOpenHashMap<>();
// Cache
private static Map<Integer, List<Integer>> fetters = new HashMap<>();
public static Int2ObjectMap<?> getMapByResourceDef(Class<?> resourceDefinition) { public static Int2ObjectMap<?> getMapByResourceDef(Class<?> resourceDefinition) {
Int2ObjectMap<?> map = null; Int2ObjectMap<?> map = null;
...@@ -221,4 +227,17 @@ public class GenshinData { ...@@ -221,4 +227,17 @@ public class GenshinData {
public static Int2ObjectMap<SceneData> getSceneDataMap() { public static Int2ObjectMap<SceneData> getSceneDataMap() {
return sceneDataMap; return sceneDataMap;
} }
public static Map<Integer, List<Integer>> getFetterDataEntries() {
if (fetters.isEmpty()) {
fetterDataMap.forEach((k, v) -> {
if (!fetters.containsKey(v.getAvatarId())) {
fetters.put(v.getAvatarId(), new ArrayList<>());
}
fetters.get(v.getAvatarId()).add(k);
});
}
return fetters;
}
} }
...@@ -128,7 +128,13 @@ public class ResourceLoader { ...@@ -128,7 +128,13 @@ public class ResourceLoader {
private static void loadScenePoints() { private static void loadScenePoints() {
Pattern pattern = Pattern.compile("(?<=scene)(.*?)(?=_point.json)"); Pattern pattern = Pattern.compile("(?<=scene)(.*?)(?=_point.json)");
File folder = new File(Grasscutter.getConfig().RESOURCE_FOLDER + "BinOutPut/Scene/Point"); File folder = new File(Grasscutter.getConfig().RESOURCE_FOLDER + "BinOutput/Scene/Point");
if (!folder.isDirectory() || !folder.exists() || folder.listFiles() == null) {
Grasscutter.getLogger().error("Scene point files cannot be found, you cannot use teleport waypoints!");
return;
}
List<ScenePointEntry> scenePointList = new ArrayList<>(); List<ScenePointEntry> scenePointList = new ArrayList<>();
for (File file : folder.listFiles()) { for (File file : folder.listFiles()) {
ScenePointConfig config = null; ScenePointConfig config = null;
......
...@@ -55,6 +55,8 @@ public class AvatarData extends GenshinResource { ...@@ -55,6 +55,8 @@ public class AvatarData extends GenshinResource {
private float[] defenseGrowthCurve; private float[] defenseGrowthCurve;
private AvatarSkillDepotData skillDepot; private AvatarSkillDepotData skillDepot;
private IntList abilities; private IntList abilities;
private List<Integer> fetters;
@Override @Override
public int getId(){ public int getId(){
...@@ -193,9 +195,16 @@ public class AvatarData extends GenshinResource { ...@@ -193,9 +195,16 @@ public class AvatarData extends GenshinResource {
return abilities; return abilities;
} }
public List<Integer> getFetters() {
return fetters;
}
@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
this.fetters = GenshinData.getFetterDataEntries().get(this.Id);
int size = GenshinData.getAvatarCurveDataMap().size(); int size = GenshinData.getAvatarCurveDataMap().size();
this.hpGrowthCurve = new float[size]; this.hpGrowthCurve = new float[size];
......
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