Merge branch 'development' into api

This commit is contained in:
Magix 2022-04-30 16:22:00 -04:00 committed by GitHub
commit be507310a5
42 changed files with 655 additions and 165 deletions

View File

@ -115,7 +115,7 @@ There is a dummy user named "Server" in every player's friends list that you can
| drop | drop <itemID\|itemName> [amount] | server.drop | Client only | Drops an item around you. | `d` `dropitem` |
| give | give [player] <itemId\|itemName> [amount] [level] [finement] | player.give | Both side | Gives item(s) to you or the specified player. (finement option only weapon.) | `g` `item` `giveitem` |
| givechar | givechar \<uid> \<avatarId> | player.givechar | Both side | Gives the player a specified character. | givec |
| giveart | giveart [player] \<artifactId> \<mainPropId> [\<appendPropId>[,\<times>]]... [level] | player.giveart | Both side | Gives the player a specified reliquary. | givea |
| giveart | giveart [player] \<artifactId> \<mainPropId> [\<appendPropId>[,\<times>]]... [level] | player.giveart | Both side | Gives the player a specified artifact. | gart |
| giveall | giveall [uid] [amount] | player.giveall | Both side | Gives all items. | givea |
| godmode | godmode [uid] | player.godmode | Client only | Prevents you from taking damage. | |
| heal | heal | player.heal | Client only | Heals all characters in your current team. | h |
@ -135,7 +135,7 @@ There is a dummy user named "Server" in every player's friends list that you can
| spawn | spanw <entityID\|entityName> [level] [amount] | server.spawn | Client only | Spawns an entity near you | |
| stop | stop | server.stop | Both side | Stops the server | |
| talent | talent \<talentID> \<value> | player.settalent | Client only | Sets talent level for your currently selected character | |
| teleport | teleport \<x> \<y> \<z> | player.teleport | Client only | Change the player's position. | tp |
| teleport | teleport [@playerUid] \<x> \<y> \<z> [sceneId] | player.teleport | Both side | Change the player's position. | tp |
| tpall | | player.tpall | Client only | Teleports all players in your world to your position | |
| weather | weather \<weatherID> \<climateID> | player.weather | Client only | Changes the weather | w |
@ -144,9 +144,12 @@ There is a dummy user named "Server" in every player's friends list that you can
When you want to teleport to somewhere, use the ingame marking function on Map, click Confirm. You will see your
character falling from a very high destination, exact location that you marked.
You can also specify a set Y coordinate by renaming the map marker.
# Quick Troubleshooting
* If compiling wasn't successful, please check your JDK installation (JDK 17 and validated JDK's bin PATH variable)
* My client doesn't connect, doesn't login, 4206, etc... - Mostly your proxy daemon setup is *the issue*, if using
Fiddler make sure it running on another port except 8888
* Startup sequence: Mongodb -> Grasscutter -> Proxy daemon (mitmdump, fiddler, etc.) -> Client
* Startup sequence: Mongodb > Grasscutter > Proxy daemon (mitmdump, fiddler, etc.) > Game

View File

@ -105,7 +105,7 @@ chmod +x gradlew
在每个玩家的朋友列表中都有一个名为“服务器”的虚拟用户,你可以通过发送消息来使用命令。命令也适用于其他聊天室,例如私人/团队聊天。
要在游戏中使用命令,需要添加 `/``!` 前缀,如 `/pos`
| 命令 | 用法 | 权限节点 | 可用性 | 注释 | 别名 |
| 命令 | 用法 | 权限节点 | 可用性 | 注释 | 别名 |
| -------------- | -------------------------------------------- | ------------------------- | -------- | ------------------------------------------ | ----------------------------------------------- |
| account | account <create\|delete> <用户名> [uid] | | 仅服务端 | 通过指定用户名和uid增删账户 | |
| broadcast | broadcast <消息内容> | server.broadcast | 均可使用 | 给所有玩家发送公告 | b |
@ -116,7 +116,7 @@ chmod +x gradlew
| drop | drop <物品ID\|物品名称> [数量] | server.drop | 仅客户端 | 在指定玩家周围掉落指定物品 | `d` `dropitem` |
| give | give [uid] <物品ID\|物品名称> [数量] [等级] [精炼等级] | | | 给予指定玩家一定数量及等级的物品 (精炼等级仅适用于武器) | `g` `item` `giveitem` |
| givechar | givechar \<uid> <角色ID> [等级] | player.givechar | 均可使用 | 给予指定玩家对应角色 | givec |
| giveart | giveart [uid] \<圣遗物ID> \<主属性ID> [\<副属性ID>[,<次数>]]... [等级] | player.giveart | 均可使用 | 给予玩家指定属性的圣遗物 | givea |
| giveart | giveart [uid] \<圣遗物ID> \<主属性ID> [\<副属性ID>[,<次数>]]... [等级] | player.giveart | 均可使用 | 给予玩家指定属性的圣遗物 | gart |
| giveall | giveall [uid] [数量] | player.giveall | 均可使用 | 给予指定玩家全部物品 | givea |
| godmode | godmode [uid] | player.godmode | 仅客户端 | 保护你不受到任何伤害(依然会被击退) | |
| heal | heal | player.heal | 仅客户端 | 治疗队伍中所有角色 | h |
@ -136,7 +136,7 @@ chmod +x gradlew
| spawn | spanw <实体ID\|实体名称> [等级] [数量] | server.spawn | 仅客户端 | 在你周围生成实体 | |
| stop | stop | server.stop | 均可使用 | 停止服务器 | |
| talent | talent <天赋ID> <等级> | player.settalent | 仅客户端 | 设置当前角色的天赋等级 | |
| teleport | teleport \<x> \<y> \<z> | player.teleport | 仅客户端 | 传送玩家到指定坐标 | tp |
| teleport | teleport [@playerUid] \<x> \<y> \<z> [sceneId] | player.teleport | 均可使用 | 传送玩家到指定坐标 | tp |
| tpall | | player.tpall | 仅客户端 | 传送多人世界中所有的玩家到自身地点 | |
| weather | weather <天气ID> <气候ID> | player.weather | 仅客户端 | 改变天气 | w |

View File

@ -41,7 +41,7 @@ sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
group = 'tech.xigam'
version = '1.0.1-dev'
version = '1.0.2-dev'
sourceCompatibility = 17
targetCompatibility = 17

View File

@ -1,5 +1,7 @@
package emu.grasscutter;
import emu.grasscutter.game.mail.Mail;
public final class Config {
public String DatabaseUrl = "mongodb://localhost:27017";
@ -70,10 +72,15 @@ public final class Config {
public int MaxAvatarsInTeamMultiplayer = 4;
public int MaxEntityLimit = 1000; // Max entity limit per world. // TODO: Enforce later.
public boolean WatchGacha = false;
public String ServerNickname = "Server";
public int ServerAvatarId = 10000007;
public int[] WelcomeEmotes = {2007, 1002, 4010};
public String WelcomeMotd = "Welcome to Grasscutter emu";
public String WelcomeMailContent = "Hi there!\r\nFirst of all, welcome to Grasscutter. If you have any issues, please let us know so that Lawnmower can help you! \r\n\r\nCheck out our:\r\n<type=\"browser\" text=\"Discord\" href=\"https://discord.gg/T5vZU6UyeG\"/> <type=\"browser\" text=\"GitHub\" href=\"https://github.com/Melledy/Grasscutter\"/>";
public int[] WelcomeMailItems = {13509};
public Mail.MailItem[] WelcomeMailItems = {
new Mail.MailItem(13509, 1, 1),
new Mail.MailItem(201, 10000, 1),
};
public boolean EnableOfficialShop = true;

View File

@ -11,6 +11,8 @@ public final class GameConstants {
public static final int MAX_TEAMS = 4;
public static final int MAIN_CHARACTER_MALE = 10000005;
public static final int MAIN_CHARACTER_FEMALE = 10000007;
public static final String SERVER_AVATAR_NAME = Grasscutter.getConfig().getGameServerOptions().ServerNickname;
public static final int SERVER_AVATAR_ID = Grasscutter.getConfig().getGameServerOptions().ServerAvatarId;
public static final Position START_POSITION = new Position(2747, 194, -1719);
public static final int MAX_FRIENDS = 45;

View File

@ -6,6 +6,7 @@ import java.io.FileReader;
import java.io.FileWriter;
import java.io.InputStreamReader;
import java.net.InetSocketAddress;
import java.util.Calendar;
import emu.grasscutter.command.CommandMap;
import emu.grasscutter.plugin.PluginManager;
@ -32,6 +33,8 @@ public final class Grasscutter {
private static final Gson gson = new GsonBuilder().setPrettyPrinting().create();
private static final File configFile = new File("./config.json");
private static int day; // Current day of week
public static RunMode MODE = RunMode.BOTH;
private static DispatchServer dispatchServer;
private static GameServer gameServer;
@ -67,8 +70,10 @@ public final class Grasscutter {
Grasscutter.getLogger().info("Starting Grasscutter...");
// Load all resources.
Grasscutter.updateDayOfWeek();
ResourceLoader.loadAll();
ScriptLoader.init();
// Database
DatabaseManager.initialize();
@ -179,4 +184,13 @@ public final class Grasscutter {
public static PluginManager getPluginManager() {
return pluginManager;
}
public static void updateDayOfWeek() {
Calendar calendar = Calendar.getInstance();
day = calendar.get(Calendar.DAY_OF_WEEK);
}
public static int getCurrentDayOfWeek() {
return day;
}
}

View File

@ -98,14 +98,14 @@ public final class GiveAllCommand implements CommandHandler {
if (isTestItem(itemdata.getId())) continue;
if (itemdata.isEquip()) {
for (int i = 0; i < 5; ++i) {
GameItem item = new GameItem(itemdata);
if (itemdata.getItemType() == ItemType.ITEM_WEAPON) {
if (itemdata.getItemType() == ItemType.ITEM_WEAPON) {
for (int i = 0; i < 5; ++i) {
GameItem item = new GameItem(itemdata);
item.setLevel(90);
item.setPromoteLevel(6);
item.setRefinement(4);
itemList.add(item);
}
itemList.add(item);
}
}
else {

View File

@ -14,13 +14,13 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@Command(label = "giveart", usage = "giveart [player] <artifactId> <mainPropId> [<appendPropId>[,<times>]]... [level]", description = "Gives the player a specified reliquary", aliases = {"givea"}, permission = "player.giveart")
@Command(label = "giveart", usage = "giveart [player] <artifactId> <mainPropId> [<appendPropId>[,<times>]]... [level]", description = "Gives the player a specified artifact", aliases = {"gart"}, permission = "player.giveart")
public final class GiveArtifactCommand implements CommandHandler {
@Override
public void execute(Player sender, List<String> args) {
int size = args.size(), target, itemId, mainPropId, level;
int size = args.size(), target, itemId, mainPropId, level = 1;
ArrayList<Integer> appendPropIdList = new ArrayList<>();
String msg = "Usage: giveart|givea [player] <artifactId> <mainPropId> [<appendPropId>[,<times>]]... [level]";
String msg = "Usage: giveart|gart [player] <artifactId> <mainPropId> [<appendPropId>[,<times>]]... [level]";
if (sender == null && size < 2) {
CommandHandler.sendMessage(null, msg);
@ -29,9 +29,14 @@ public final class GiveArtifactCommand implements CommandHandler {
if (size >= 2) {
try {
level = Integer.parseInt(args.get(size - 1));
if (level <= 21) size--;
else level = 1;
try {
int last = Integer.parseInt(args.get(size - 1));
if (last >= 1 && last <= 21) {
level = last;
size--;
}
} catch (NumberFormatException ignored) {
}
target = Integer.parseInt(args.get(0));
int fromIdx;
if (Grasscutter.getGameServer().getPlayerByUid(target) == null && sender != null) {
@ -79,6 +84,7 @@ public final class GiveArtifactCommand implements CommandHandler {
GameItem item = new GameItem(itemData);
item.setLevel(level);
item.setMainPropId(mainPropId);
item.getAppendPropIdList().clear();
item.getAppendPropIdList().addAll(appendPropIdList);
targetPlayer.getInventory().addItem(item, ActionReason.SubfieldDrop);

View File

@ -163,20 +163,28 @@ public final class GiveCommand implements CommandHandler {
List<GameItem> items = new LinkedList<>();
for (int i = 0; i < amount; i++) {
GameItem item = new GameItem(itemData);
if (item.isEquipped()) {
// check item max level
if (item.getItemType() == ItemType.ITEM_WEAPON) {
if (lvl > 90) lvl = 90;
} else {
if (lvl > 21) lvl = 21;
}
}
item.setCount(amount);
item.setLevel(lvl);
if (lvl > 20 && lvl < 40) {
item.setPromoteLevel(1);
} else if (lvl > 40 && lvl <= 50) {
item.setPromoteLevel(2);
} else if (lvl > 50 && lvl <= 60) {
item.setPromoteLevel(3);
} else if (lvl > 60 && lvl <= 70) {
item.setPromoteLevel(4);
} else if (lvl > 70 && lvl <= 80) {
item.setPromoteLevel(5);
} else if (lvl > 80 && lvl <= 90) {
if (lvl > 80) {
item.setPromoteLevel(6);
} else if (lvl > 70) {
item.setPromoteLevel(5);
} else if (lvl > 60) {
item.setPromoteLevel(4);
} else if (lvl > 50) {
item.setPromoteLevel(3);
} else if (lvl > 40) {
item.setPromoteLevel(2);
} else if (lvl > 20) {
item.setPromoteLevel(1);
}
if (item.getItemType() == ItemType.ITEM_WEAPON) {
if (refinement > 0) {

View File

@ -8,20 +8,43 @@ import emu.grasscutter.game.player.Player;
import java.util.List;
import java.util.Map;
@Command(label = "list", description = "List online players")
@Command(label = "list", usage = "list [uid]",
description = "List online players", aliases = {"players"})
public final class ListCommand implements CommandHandler {
@Override
public void execute(Player sender, List<String> args) {
Map<Integer, Player> playersMap = Grasscutter.getGameServer().getPlayers();
boolean needUID = false;
if (args.size() > 0) {
needUID = args.get(0).equals("uid");
}
CommandHandler.sendMessage(sender, String.format("There are %s player(s) online:", playersMap.size()));
if (playersMap.size() != 0) {
StringBuilder playerSet = new StringBuilder();
playersMap.values().forEach(player ->
playerSet.append(player.getNickname()).append(", "));
boolean finalNeedUID = needUID;
playersMap.values().forEach(player -> {
playerSet.append(player.getNickname());
if (finalNeedUID) {
if (sender != null) {
playerSet.append(" <color=green>(")
.append(player.getUid())
.append(")</color>");
} else {
playerSet.append(" (")
.append(player.getUid())
.append(")");
}
}
playerSet.append(", ");
});
String players = playerSet.toString();
CommandHandler.sendMessage(sender, players.substring(0, players.length() - 2));
}

View File

@ -1,5 +1,6 @@
package emu.grasscutter.command.commands;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.command.Command;
import emu.grasscutter.command.CommandHandler;
import emu.grasscutter.game.player.Player;
@ -7,21 +8,39 @@ import emu.grasscutter.utils.Position;
import java.util.List;
@Command(label = "teleport", usage = "teleport <x> <y> <z>", aliases = {"tp"},
@Command(label = "teleport", usage = "teleport [@player id] <x> <y> <z> [scene id]", aliases = {"tp"},
description = "Change the player's position.", permission = "player.teleport")
public final class TeleportCommand implements CommandHandler {
@Override
public void execute(Player sender, List<String> args) {
if (sender == null) {
CommandHandler.sendMessage(null, "Run this command in-game.");
int target;
if (args.size() < (sender == null ? 4 : 3)) {
CommandHandler.sendMessage(sender, sender == null ? "Usage: /tp @<player id> <x> <y> <z> [scene id]" :
"Usage: /tp [@<player id>] <x> <y> <z> [scene id]");
return;
}
if (args.get(0).startsWith("@")) {
try {
target = Integer.parseInt(args.get(0).substring(1));
} catch (NumberFormatException e) {
CommandHandler.sendMessage(sender, "Invalid player id.");
return;
}
} else {
if (sender == null) {
CommandHandler.sendMessage(null, "You must specify a player id.");
return;
}
target = sender.getUid();
}
if (args.size() < 3){
CommandHandler.sendMessage(sender, "Usage: /tp <x> <y> <z> [scene id]");
Player targetPlayer = Grasscutter.getGameServer().getPlayerByUid(target);
if (targetPlayer == null) {
CommandHandler.sendMessage(sender, "Player not found or offline.");
return;
}
args = args.subList(args.get(0).startsWith("@") ? 1 : 0, args.size());
try {
float x = 0f;
@ -29,39 +48,41 @@ public final class TeleportCommand implements CommandHandler {
float z = 0f;
if (args.get(0).contains("~")) {
if (args.get(0).equals("~")) {
x = sender.getPos().getX();
x = targetPlayer.getPos().getX();
} else {
x = Float.parseFloat(args.get(0).replace("~", "")) + sender.getPos().getX();
x = Float.parseFloat(args.get(0).replace("~", "")) + targetPlayer.getPos().getX();
}
} else {
x = Float.parseFloat(args.get(0));
}
if (args.get(1).contains("~")) {
if (args.get(1).equals("~")) {
y = sender.getPos().getY();
y = targetPlayer.getPos().getY();
} else {
y = Float.parseFloat(args.get(1).replace("~", "")) + sender.getPos().getY();
y = Float.parseFloat(args.get(1).replace("~", "")) + targetPlayer.getPos().getY();
}
} else {
y = Float.parseFloat(args.get(1));
}
if (args.get(2).contains("~")) {
if (args.get(2).equals("~")) {
z = sender.getPos().getZ();
z = targetPlayer.getPos().getZ();
} else {
z = Float.parseFloat(args.get(2).replace("~", "")) + sender.getPos().getZ();
z = Float.parseFloat(args.get(2).replace("~", "")) + targetPlayer.getPos().getZ();
}
} else {
z = Float.parseFloat(args.get(2));
}
int sceneId = sender.getSceneId();
int sceneId = targetPlayer.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);
Position target_pos = new Position(x, y, z);
boolean result = targetPlayer.getWorld().transferPlayerToScene(targetPlayer, sceneId, target_pos);
if (!result) {
CommandHandler.sendMessage(sender, "Invalid position.");
} else {
CommandHandler.sendMessage(sender, "Teleported " + targetPlayer.getNickname() + " to " + x + "," + y + "," + z + " in scene " + sceneId);
}
} catch (NumberFormatException ignored) {
CommandHandler.sendMessage(sender, "Invalid position.");

View File

@ -15,6 +15,8 @@ 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;
import it.unimi.dsi.fastutil.ints.IntArrayList;
import it.unimi.dsi.fastutil.ints.IntList;
public class GameData {
// BinOutputs
@ -61,12 +63,14 @@ public class GameData {
private static final Int2ObjectMap<FetterCharacterCardData> fetterCharacterCardDataMap = new Int2ObjectOpenHashMap<>();
private static final Int2ObjectMap<RewardData> rewardDataMap = new Int2ObjectOpenHashMap<>();
private static final Int2ObjectMap<WorldLevelData> worldLevelDataMap = new Int2ObjectOpenHashMap<>();
private static final Int2ObjectMap<DailyDungeonData> dailyDungeonDataMap = new Int2ObjectOpenHashMap<>();
private static final Int2ObjectMap<DungeonData> dungeonDataMap = new Int2ObjectOpenHashMap<>();
private static final Int2ObjectMap<ShopGoodsData> shopGoodsDataMap = new Int2ObjectOpenHashMap<>();
// Cache
private static Map<Integer, List<Integer>> fetters = new HashMap<>();
private static Map<Integer, List<ShopGoodsData>> shopGoods = new HashMap<>();
private static final IntList scenePointIdList = new IntArrayList();
public static char EJWOA = 's';
@ -280,6 +284,10 @@ public class GameData {
return dungeonDataMap;
}
public static Int2ObjectMap<DailyDungeonData> getDailyDungeonDataMap() {
return dailyDungeonDataMap;
}
public static Map<Integer, List<ShopGoodsData>> getShopGoodsDataEntries() {
if (shopGoods.isEmpty()) {
shopGoodsDataMap.forEach((k, v) -> {
@ -291,4 +299,8 @@ public class GameData {
return shopGoods;
}
public static IntList getScenePointIdList() {
return scenePointIdList;
}
}

View File

@ -48,11 +48,12 @@ public class ResourceLoader {
loadOpenConfig();
// Load resources
loadResources();
loadScenePoints();
// Process into depots
GameDepot.load();
// Load spawn data
loadSpawnData();
// Load scene points - must be done AFTER resources are loaded
loadScenePoints();
// Custom - TODO move this somewhere else
try {
GameData.getAvatarSkillDepotDataMap().get(504).setAbilities(
@ -168,6 +169,9 @@ public class ResourceLoader {
ScenePointEntry sl = new ScenePointEntry(sceneId + "_" + entry.getKey(), pointData);
scenePointList.add(sl);
GameData.getScenePointIdList().add(pointData.getId());
pointData.updateDailyDungeon();
}
for (ScenePointEntry entry : scenePointList) {

View File

@ -1,12 +1,18 @@
package emu.grasscutter.data.common;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.data.GameData;
import emu.grasscutter.data.def.DailyDungeonData;
import emu.grasscutter.utils.Position;
import it.unimi.dsi.fastutil.ints.IntArrayList;
import it.unimi.dsi.fastutil.ints.IntList;
public class PointData {
private int id;
private String $type;
private Position tranPos;
private int[] dungeonIds;
private int[] dungeonRandomList;
public int getId() {
return id;
@ -27,4 +33,31 @@ public class PointData {
public int[] getDungeonIds() {
return dungeonIds;
}
public int[] getDungeonRandomList() {
return dungeonRandomList;
}
public void updateDailyDungeon() {
if (getDungeonRandomList() == null) {
return;
}
IntList newDungeons = new IntArrayList();
int day = Grasscutter.getCurrentDayOfWeek();
for (int randomId : getDungeonRandomList()) {
DailyDungeonData data = GameData.getDailyDungeonDataMap().get(randomId);
if (data != null) {
int[] addDungeons = data.getDungeonsByDay(day);
for (int d : addDungeons) {
newDungeons.add(d);
}
}
}
this.dungeonIds = newDungeons.toIntArray();
}
}

View File

@ -0,0 +1,50 @@
package emu.grasscutter.data.def;
import java.util.Calendar;
import emu.grasscutter.data.GameData;
import emu.grasscutter.data.GameResource;
import emu.grasscutter.data.ResourceType;
import emu.grasscutter.game.props.SceneType;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
@ResourceType(name = "DailyDungeonConfigData.json")
public class DailyDungeonData extends GameResource {
private int Id;
private int[] Monday;
private int[] Tuesday;
private int[] Wednesday;
private int[] Thursday;
private int[] Friday;
private int[] Saturday;
private int[] Sunday;
private static final int[] empty = new int[0];
private final Int2ObjectMap<int[]> map;
public DailyDungeonData() {
this.map = new Int2ObjectOpenHashMap<>();
}
@Override
public int getId() {
return this.Id;
}
public int[] getDungeonsByDay(int day) {
return map.getOrDefault(day, empty);
}
@Override
public void onLoad() {
map.put(Calendar.MONDAY, Monday);
map.put(Calendar.TUESDAY, Tuesday);
map.put(Calendar.WEDNESDAY, Wednesday);
map.put(Calendar.THURSDAY, Thursday);
map.put(Calendar.FRIDAY, Friday);
map.put(Calendar.SATURDAY, Saturday);
map.put(Calendar.SUNDAY, Sunday);
}
}

View File

@ -104,7 +104,10 @@ public class Account {
}
public boolean hasPermission(String permission) {
return this.permissions.contains(permission) || this.permissions.contains("*");
return this.permissions.contains(permission) ||
this.permissions.contains("*") ||
(this.permissions.contains("player") || this.permissions.contains("player.*")) && permission.startsWith("player.") ||
(this.permissions.contains("server") || this.permissions.contains("server.*")) && permission.startsWith("server.");
}
public boolean removePermission(String permission) {

View File

@ -15,7 +15,6 @@ import dev.morphia.annotations.Indexed;
import dev.morphia.annotations.PostLoad;
import dev.morphia.annotations.PrePersist;
import dev.morphia.annotations.Transient;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.data.GameData;
import emu.grasscutter.data.common.FightPropData;
import emu.grasscutter.data.custom.OpenConfigEntry;
@ -26,18 +25,19 @@ import emu.grasscutter.data.def.AvatarSkillDepotData;
import emu.grasscutter.data.def.AvatarSkillDepotData.InherentProudSkillOpens;
import emu.grasscutter.data.def.AvatarTalentData;
import emu.grasscutter.data.def.EquipAffixData;
import emu.grasscutter.data.def.ItemData.WeaponProperty;
import emu.grasscutter.data.def.ProudSkillData;
import emu.grasscutter.data.def.ReliquaryAffixData;
import emu.grasscutter.data.def.ReliquaryLevelData;
import emu.grasscutter.data.def.ReliquaryMainPropData;
import emu.grasscutter.data.def.ReliquarySetData;
import emu.grasscutter.data.def.WeaponCurveData;
import emu.grasscutter.data.def.WeaponPromoteData;
import emu.grasscutter.data.def.ItemData.WeaponProperty;
import emu.grasscutter.data.def.ProudSkillData;
import emu.grasscutter.database.DatabaseHelper;
import emu.grasscutter.game.entity.EntityAvatar;
import emu.grasscutter.game.inventory.EquipType;
import emu.grasscutter.game.inventory.GameItem;
import emu.grasscutter.game.inventory.ItemType;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.props.ElementType;
import emu.grasscutter.game.props.EntityIdType;
@ -45,8 +45,11 @@ import emu.grasscutter.game.props.FetterState;
import emu.grasscutter.game.props.FightProperty;
import emu.grasscutter.game.props.PlayerProperty;
import emu.grasscutter.net.proto.AvatarFetterInfoOuterClass.AvatarFetterInfo;
import emu.grasscutter.net.proto.FetterDataOuterClass.FetterData;
import emu.grasscutter.net.proto.AvatarInfoOuterClass.AvatarInfo;
import emu.grasscutter.net.proto.FetterDataOuterClass.FetterData;
import emu.grasscutter.net.proto.ShowAvatarInfoOuterClass;
import emu.grasscutter.net.proto.ShowAvatarInfoOuterClass.ShowAvatarInfo;
import emu.grasscutter.net.proto.ShowEquipOuterClass.ShowEquip;
import emu.grasscutter.server.packet.send.PacketAbilityChangeNotify;
import emu.grasscutter.server.packet.send.PacketAvatarEquipChangeNotify;
import emu.grasscutter.server.packet.send.PacketAvatarFightPropNotify;
@ -797,6 +800,46 @@ public class Avatar {
return avatarInfo.build();
}
// used only in character showcase
public ShowAvatarInfo toShowAvatarInfoProto() {
AvatarFetterInfo.Builder avatarFetter = AvatarFetterInfo.newBuilder()
.setExpLevel(this.getFetterLevel());
ShowAvatarInfo.Builder showAvatarInfo = ShowAvatarInfoOuterClass.ShowAvatarInfo.newBuilder()
.setAvatarId(avatarId)
.addAllTalentIdList(this.getTalentIdList())
.putAllFightPropMap(this.getFightProperties())
.setSkillDepotId(this.getSkillDepotId())
.setCoreProudSkillLevel(this.getCoreProudSkillLevel())
.addAllInherentProudSkillList(this.getProudSkillList())
.putAllSkillLevelMap(this.getSkillLevelMap())
.putAllProudSkillExtraLevelMap(this.getProudSkillBonusMap())
.setFetterInfo(avatarFetter)
.setCostumeId(this.getCostume());
showAvatarInfo.putPropMap(PlayerProperty.PROP_LEVEL.getId(), ProtoHelper.newPropValue(PlayerProperty.PROP_LEVEL, this.getLevel()));
showAvatarInfo.putPropMap(PlayerProperty.PROP_EXP.getId(), ProtoHelper.newPropValue(PlayerProperty.PROP_EXP, this.getExp()));
showAvatarInfo.putPropMap(PlayerProperty.PROP_BREAK_LEVEL.getId(), ProtoHelper.newPropValue(PlayerProperty.PROP_BREAK_LEVEL, this.getPromoteLevel()));
showAvatarInfo.putPropMap(PlayerProperty.PROP_SATIATION_VAL.getId(), ProtoHelper.newPropValue(PlayerProperty.PROP_SATIATION_VAL, this.getSatiation()));
showAvatarInfo.putPropMap(PlayerProperty.PROP_SATIATION_PENALTY_TIME.getId(), ProtoHelper.newPropValue(PlayerProperty.PROP_SATIATION_VAL, this.getSatiationPenalty()));
int maxStamina = this.getPlayer().getProperty(PlayerProperty.PROP_MAX_STAMINA);
showAvatarInfo.putPropMap(PlayerProperty.PROP_MAX_STAMINA.getId(), ProtoHelper.newPropValue(PlayerProperty.PROP_MAX_STAMINA, maxStamina));
for (GameItem item : this.getEquips().values()) {
if (item.getItemType() == ItemType.ITEM_RELIQUARY) {
showAvatarInfo.addEquipList(ShowEquip.newBuilder()
.setItemId(item.getItemId())
.setReliquary(item.toReliquaryProto()));
} else if (item.getItemType() == ItemType.ITEM_WEAPON) {
showAvatarInfo.addEquipList(ShowEquip.newBuilder()
.setItemId(item.getItemId())
.setWeapon(item.toWeaponProto()));
}
}
return showAvatarInfo.build();
}
@PostLoad
private void onLoad() {

View File

@ -28,7 +28,7 @@ public class DungeonManager {
public void getEntryInfo(Player player, int pointId) {
ScenePointEntry entry = GameData.getScenePointEntryById(player.getScene().getId(), pointId);
if (entry == null || entry.getPointData().getDungeonIds() == null) {
if (entry == null) {
// Error
player.sendPacket(new PacketDungeonEntryInfoRsp());
return;
@ -79,4 +79,10 @@ public class DungeonManager {
player.getWorld().transferPlayerToScene(player, prevScene, prevPos);
player.sendPacket(new BasePacket(PacketOpcodes.PlayerQuitDungeonRsp));
}
public void updateDailyDungeons() {
for (ScenePointEntry entry : GameData.getScenePointEntries().values()) {
entry.getPointData().updateDailyDungeon();
}
}
}

View File

@ -34,6 +34,10 @@ public abstract class GameEntity {
return this.id;
}
public int getEntityType() {
return getId() >> 24;
}
public World getWorld() {
return this.getScene().getWorld();
}

View File

@ -375,6 +375,32 @@ public class GameItem {
return relicInfo;
}
public Weapon toWeaponProto() {
Weapon.Builder weapon = Weapon.newBuilder()
.setLevel(this.getLevel())
.setExp(this.getExp())
.setPromoteLevel(this.getPromoteLevel());
if (this.getAffixes() != null && this.getAffixes().size() > 0) {
for (int affix : this.getAffixes()) {
weapon.putAffixMap(affix, this.getRefinement());
}
}
return weapon.build();
}
public Reliquary toReliquaryProto() {
Reliquary.Builder relic = Reliquary.newBuilder()
.setLevel(this.getLevel())
.setExp(this.getExp())
.setPromoteLevel(this.getPromoteLevel())
.setMainPropId(this.getMainPropId())
.addAllAppendPropIdList(this.getAppendPropIdList());
return relic.build();
}
public Item toProto() {
Item.Builder proto = Item.newBuilder()
.setGuid(this.getGuid())
@ -382,27 +408,11 @@ public class GameItem {
switch (getItemType()) {
case ITEM_WEAPON:
Weapon.Builder weapon = Weapon.newBuilder()
.setLevel(this.getLevel())
.setExp(this.getExp())
.setPromoteLevel(this.getPromoteLevel());
if (this.getAffixes() != null && this.getAffixes().size() > 0) {
for (int affix : this.getAffixes()) {
weapon.putAffixMap(affix, this.getRefinement());
}
}
Weapon weapon = this.toWeaponProto();
proto.setEquip(Equip.newBuilder().setWeapon(weapon).setIsLocked(this.isLocked()).build());
break;
case ITEM_RELIQUARY:
Reliquary relic = Reliquary.newBuilder()
.setLevel(this.getLevel())
.setExp(this.getExp())
.setPromoteLevel(this.getPromoteLevel())
.setMainPropId(this.getMainPropId())
.addAllAppendPropIdList(this.getAppendPropIdList())
.build();
Reliquary relic = this.toReliquaryProto();
proto.setEquip(Equip.newBuilder().setReliquary(relic).setIsLocked(this.isLocked()).build());
break;
case ITEM_MATERIAL:

View File

@ -21,7 +21,6 @@ import emu.grasscutter.game.inventory.Inventory;
import emu.grasscutter.game.mail.Mail;
import emu.grasscutter.game.props.ActionReason;
import emu.grasscutter.game.props.PlayerProperty;
import emu.grasscutter.game.shop.ShopInfo;
import emu.grasscutter.game.shop.ShopLimit;
import emu.grasscutter.game.world.Scene;
import emu.grasscutter.game.world.World;
@ -34,10 +33,12 @@ import emu.grasscutter.net.proto.OnlinePlayerInfoOuterClass.OnlinePlayerInfo;
import emu.grasscutter.net.proto.PlayerApplyEnterMpResultNotifyOuterClass;
import emu.grasscutter.net.proto.PlayerLocationInfoOuterClass.PlayerLocationInfo;
import emu.grasscutter.net.proto.PlayerWorldLocationInfoOuterClass;
import emu.grasscutter.net.proto.ShowAvatarInfoOuterClass;
import emu.grasscutter.net.proto.ProfilePictureOuterClass.ProfilePicture;
import emu.grasscutter.net.proto.SocialDetailOuterClass.SocialDetail;
import emu.grasscutter.net.proto.SocialShowAvatarInfoOuterClass;
import emu.grasscutter.server.event.game.PlayerQuitEvent;
import emu.grasscutter.server.event.player.PlayerJoinEvent;
import emu.grasscutter.server.event.player.PlayerQuitEvent;
import emu.grasscutter.server.event.player.PlayerReceiveMailEvent;
import emu.grasscutter.server.game.GameServer;
import emu.grasscutter.server.game.GameSession;
@ -828,7 +829,7 @@ public class Player {
.setProfilePicture(ProfilePicture.newBuilder().setAvatarId(this.getHeadImage()));
if (this.getWorld() != null) {
onlineInfo.setCurPlayerNumInWorld(this.getWorld().getPlayers().indexOf(this) + 1);
onlineInfo.setCurPlayerNumInWorld(getWorld().getPlayerCount());
} else {
onlineInfo.setCurPlayerNumInWorld(1);
}
@ -904,6 +905,35 @@ public class Player {
.setFinishAchievementNum(0);
return social;
}
public List<ShowAvatarInfoOuterClass.ShowAvatarInfo> getShowAvatarInfoList() {
List<ShowAvatarInfoOuterClass.ShowAvatarInfo> showAvatarInfoList = new ArrayList<>();
Player player;
boolean shouldRecalc;
if (this.isOnline()) {
player = this;
shouldRecalc = false;
} else {
player = DatabaseHelper.getPlayerById(id);
player.getAvatars().loadFromDatabase();
player.getInventory().loadFromDatabase();
shouldRecalc = true;
}
List<Integer> showAvatarList = player.getShowAvatarList();
AvatarStorage avatars = player.getAvatars();
if (showAvatarList != null) {
for (int avatarId : showAvatarList) {
Avatar avatar = avatars.getAvatarById(avatarId);
if (shouldRecalc) {
avatar.recalcStats();
}
showAvatarInfoList.add(avatar.toShowAvatarInfoProto());
}
}
return showAvatarInfoList;
}
public PlayerWorldLocationInfoOuterClass.PlayerWorldLocationInfo getWorldPlayerLocationInfo() {
return PlayerWorldLocationInfoOuterClass.PlayerWorldLocationInfo.newBuilder()
@ -1014,6 +1044,11 @@ public class Player {
// First notify packets sent
this.setHasSentAvatarDataNotify(true);
// Call join event.
PlayerJoinEvent event = new PlayerJoinEvent(this); event.call();
if(event.isCanceled()) // If event is not cancelled, continue.
session.close();
}
public void onLogout() {

View File

@ -375,8 +375,8 @@ public class Scene {
this.broadcastPacket(new PacketLifeStateChangeNotify(attackerId, target, LifeState.LIFE_DEAD));
// Reward drop
if (target instanceof EntityMonster) {
Grasscutter.getGameServer().getDropManager().callDrop((EntityMonster) target);
if (target instanceof EntityMonster && this.getSceneType() != SceneType.SCENE_DUNGEON) {
getWorld().getServer().getDropManager().callDrop((EntityMonster) target);
}
this.removeEntity(target);
@ -508,6 +508,7 @@ public class Scene {
}
group.triggers.forEach(getScriptManager()::registerTrigger);
group.regions.forEach(getScriptManager()::registerRegion);
}
// Spawn gadgets AFTER triggers are added
@ -526,6 +527,7 @@ public class Scene {
for (SceneGroup group : block.groups) {
group.triggers.forEach(getScriptManager()::deregisterTrigger);
group.regions.forEach(getScriptManager()::deregisterRegion);
}
}

View File

@ -27,6 +27,7 @@ import emu.grasscutter.net.proto.AttackResultOuterClass.AttackResult;
import emu.grasscutter.net.proto.EnterTypeOuterClass.EnterType;
import emu.grasscutter.net.proto.VisionTypeOuterClass.VisionType;
import emu.grasscutter.scripts.data.SceneConfig;
import emu.grasscutter.server.game.GameServer;
import emu.grasscutter.server.packet.send.PacketDelTeamEntityNotify;
import emu.grasscutter.server.packet.send.PacketEntityFightPropUpdateNotify;
import emu.grasscutter.server.packet.send.PacketLifeStateChangeNotify;
@ -44,6 +45,7 @@ import it.unimi.dsi.fastutil.ints.Int2ObjectMaps;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
public class World implements Iterable<Player> {
private final GameServer server;
private final Player owner;
private final List<Player> players;
private final Int2ObjectMap<Scene> scenes;
@ -61,6 +63,7 @@ public class World implements Iterable<Player> {
public World(Player player, boolean isMultiplayer) {
this.owner = player;
this.server = player.getServer();
this.players = Collections.synchronizedList(new ArrayList<>());
this.scenes = Int2ObjectMaps.synchronize(new Int2ObjectOpenHashMap<>());
@ -75,6 +78,10 @@ public class World implements Iterable<Player> {
return owner;
}
public GameServer getServer() {
return server;
}
public int getLevelEntityId() {
return levelEntityId;
}

View File

@ -1,6 +1,7 @@
package emu.grasscutter.scripts;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
@ -23,6 +24,7 @@ import emu.grasscutter.data.def.WorldLevelData;
import emu.grasscutter.game.entity.EntityGadget;
import emu.grasscutter.game.entity.EntityMonster;
import emu.grasscutter.game.entity.GameEntity;
import emu.grasscutter.game.props.EntityType;
import emu.grasscutter.game.world.Scene;
import emu.grasscutter.scripts.constants.EventType;
import emu.grasscutter.scripts.constants.ScriptGadgetState;
@ -33,6 +35,7 @@ import emu.grasscutter.scripts.data.SceneGadget;
import emu.grasscutter.scripts.data.SceneGroup;
import emu.grasscutter.scripts.data.SceneInitConfig;
import emu.grasscutter.scripts.data.SceneMonster;
import emu.grasscutter.scripts.data.SceneRegion;
import emu.grasscutter.scripts.data.SceneSuite;
import emu.grasscutter.scripts.data.SceneTrigger;
import emu.grasscutter.scripts.data.SceneVar;
@ -49,14 +52,17 @@ public class SceneScriptManager {
private Bindings bindings;
private SceneConfig config;
private List<SceneBlock> blocks;
private Int2ObjectOpenHashMap<Set<SceneTrigger>> triggers;
private boolean isInit;
private final Int2ObjectOpenHashMap<Set<SceneTrigger>> triggers;
private final Int2ObjectOpenHashMap<SceneRegion> regions;
public SceneScriptManager(Scene scene) {
this.scene = scene;
this.scriptLib = new ScriptLib(this);
this.scriptLibLua = CoerceJavaToLua.coerce(this.scriptLib);
this.triggers = new Int2ObjectOpenHashMap<>();
this.regions = new Int2ObjectOpenHashMap<>();
this.variables = new HashMap<>();
// TEMPORARY
@ -108,6 +114,18 @@ public class SceneScriptManager {
getTriggersByEvent(trigger.event).remove(trigger);
}
public SceneRegion getRegionById(int id) {
return regions.get(id);
}
public void registerRegion(SceneRegion region) {
regions.put(region.config_id, region);
}
public void deregisterRegion(SceneRegion region) {
regions.remove(region.config_id);
}
// TODO optimize
public SceneGroup getGroupById(int groupId) {
for (SceneBlock block : this.getScene().getLoadedBlocks()) {
@ -134,11 +152,8 @@ public class SceneScriptManager {
bindings = ScriptLoader.getEngine().createBindings();
// Set variables
bindings.put("EventType", new EventType()); // TODO - make static class to avoid instantiating a new class every scene
bindings.put("GadgetState", new ScriptGadgetState());
bindings.put("RegionShape", new ScriptRegionShape());
bindings.put("ScriptLib", getScriptLib());
// Eval script
try {
cs.eval(getBindings());
@ -211,6 +226,7 @@ public class SceneScriptManager {
group.gadgets = ScriptLoader.getSerializer().toList(SceneGadget.class, bindings.get("gadgets"));
group.triggers = ScriptLoader.getSerializer().toList(SceneTrigger.class, bindings.get("triggers"));
group.suites = ScriptLoader.getSerializer().toList(SceneSuite.class, bindings.get("suites"));
group.regions = ScriptLoader.getSerializer().toList(SceneRegion.class, bindings.get("regions"));
group.init_config = ScriptLoader.getSerializer().toObject(SceneInitConfig.class, bindings.get("init_config"));
// Add variables to suite
@ -235,11 +251,27 @@ public class SceneScriptManager {
}
public void onTick() {
checkTriggers();
checkRegions();
}
public void checkTriggers() {
public void checkRegions() {
if (this.regions.size() == 0) {
return;
}
for (SceneRegion region : this.regions.values()) {
getScene().getEntities().values()
.stream()
.filter(e -> e.getEntityType() <= 2 && region.contains(e.getPosition()))
.forEach(region::addEntity);
if (region.hasNewEntities()) {
// This is not how it works, source_eid should be region entity id, but we dont have an entity for regions yet
callEvent(EventType.EVENT_ENTER_REGION, new ScriptArgs(region.config_id).setSourceEntityId(region.config_id));
region.resetNewEntities();
}
}
}
public void spawnGadgetsInGroup(SceneGroup group) {

View File

@ -17,6 +17,7 @@ import emu.grasscutter.game.entity.GameEntity;
import emu.grasscutter.scripts.constants.EventType;
import emu.grasscutter.scripts.data.SceneGroup;
import emu.grasscutter.scripts.data.SceneMonster;
import emu.grasscutter.scripts.data.SceneRegion;
import emu.grasscutter.scripts.data.ScriptArgs;
import emu.grasscutter.server.packet.send.PacketGadgetStateNotify;
import emu.grasscutter.server.packet.send.PacketWorktopOptionNotify;
@ -184,6 +185,19 @@ public class ScriptLib {
return 0;
}
public int GetRegionEntityCount(LuaTable table) {
int regionId = table.get("region_eid").toint();
int entityType = table.get("entity_type").toint();
SceneRegion region = this.getSceneScriptManager().getRegionById(regionId);
if (region == null) {
return 0;
}
return (int) region.getEntities().intStream().filter(e -> e >> 24 == entityType).count();
}
public void PrintContextLog(String msg) {
Grasscutter.getLogger().info("[LUA] " + msg);
}

View File

@ -4,6 +4,7 @@ import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
@ -13,7 +14,17 @@ import javax.script.ScriptEngine;
import javax.script.ScriptEngineFactory;
import javax.script.ScriptEngineManager;
import org.luaj.vm2.LuaTable;
import org.luaj.vm2.LuaValue;
import org.luaj.vm2.lib.OneArgFunction;
import org.luaj.vm2.lib.jse.CoerceJavaToLua;
import org.luaj.vm2.script.LuajContext;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.game.props.EntityType;
import emu.grasscutter.scripts.constants.EventType;
import emu.grasscutter.scripts.constants.ScriptGadgetState;
import emu.grasscutter.scripts.constants.ScriptRegionShape;
import emu.grasscutter.scripts.serializer.LuaSerializer;
import emu.grasscutter.scripts.serializer.Serializer;
@ -31,11 +42,31 @@ public class ScriptLoader {
throw new Exception("Script loader already initialized");
}
// Create script engine
sm = new ScriptEngineManager();
engine = sm.getEngineByName("luaj");
factory = getEngine().getFactory();
// Lua stuff
fileType = "lua";
serializer = new LuaSerializer();
// Set engine to replace require as a temporary fix to missing scripts
LuajContext ctx = (LuajContext) engine.getContext();
ctx.globals.set("require", new OneArgFunction() {
@Override
public LuaValue call(LuaValue arg0) {
return LuaValue.ZERO;
}
});
LuaTable table = new LuaTable();
Arrays.stream(EntityType.values()).forEach(e -> table.set(e.name().toUpperCase(), e.getValue()));
ctx.globals.set("EntityType", table);
ctx.globals.set("EventType", CoerceJavaToLua.coerce(new EventType())); // TODO - make static class to avoid instantiating a new class every scene
ctx.globals.set("GadgetState", CoerceJavaToLua.coerce(new ScriptGadgetState()));
ctx.globals.set("RegionShape", CoerceJavaToLua.coerce(new ScriptRegionShape()));
}
public static ScriptEngine getEngine() {

View File

@ -14,6 +14,7 @@ public class SceneGroup {
public List<SceneMonster> monsters;
public List<SceneGadget> gadgets;
public List<SceneTrigger> triggers;
public List<SceneRegion> regions;
public List<SceneSuite> suites;
public SceneInitConfig init_config;

View File

@ -0,0 +1,57 @@
package emu.grasscutter.scripts.data;
import emu.grasscutter.game.entity.GameEntity;
import emu.grasscutter.scripts.constants.ScriptRegionShape;
import emu.grasscutter.utils.Position;
import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
import it.unimi.dsi.fastutil.ints.IntSet;
public class SceneRegion {
public int config_id;
public int shape;
public Position pos;
public Position size;
private boolean hasNewEntities;
private final IntSet entities; // Ids of entities inside this region
public SceneRegion() {
this.entities = new IntOpenHashSet();
}
public IntSet getEntities() {
return entities;
}
public void addEntity(GameEntity entity) {
if (this.getEntities().contains(entity.getId())) {
return;
}
this.getEntities().add(entity.getId());
this.hasNewEntities = true;
}
public void removeEntity(GameEntity entity) {
this.getEntities().remove(entity.getId());
}
public boolean contains(Position p) {
switch (shape) {
case ScriptRegionShape.CUBIC:
return (Math.abs(pos.getX() - p.getX()) <= size.getX()) &&
(Math.abs(pos.getZ() - p.getZ()) <= size.getZ());
case ScriptRegionShape.SPHERE:
return false;
}
return false;
}
public boolean hasNewEntities() {
return hasNewEntities;
}
public void resetNewEntities() {
hasNewEntities = false;
}
}

View File

@ -4,6 +4,7 @@ public class ScriptArgs {
public int param1;
public int param2;
public int param3;
public int source_eid; // Source entity
public ScriptArgs() {
@ -44,4 +45,13 @@ public class ScriptArgs {
this.param3 = param3;
return this;
}
public int getSourceEntityId() {
return source_eid;
}
public ScriptArgs setSourceEntityId(int source_eid) {
this.source_eid = source_eid;
return this;
}
}

View File

@ -455,8 +455,7 @@ public final class DispatchServer {
// hk4e-sdk-os-static.hoyoverse.com
httpServer.get("/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}}"));
// hk4e-sdk-os-static.hoyoverse.com
httpServer.get("/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}}}}"));
httpServer.get("/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}}}}"));
// Test api?
// abtest-api-data-sg.hoyoverse.com
httpServer.post("/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\"}}]}"));

View File

@ -0,0 +1,27 @@
package emu.grasscutter.server.event.game;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.server.event.types.GameEvent;
import emu.grasscutter.server.game.GameSession;
public final class PlayerCreationEvent extends GameEvent {
private final GameSession session;
private Class<? extends Player> playerClass;
public PlayerCreationEvent(GameSession session, Class<? extends Player> playerClass) {
this.session = session;
this.playerClass = playerClass;
}
public GameSession getSession() {
return this.session;
}
public void setPlayerClass(Class<? extends Player> playerClass) {
this.playerClass = playerClass;
}
public Class<? extends Player> getPlayerClass() {
return this.playerClass;
}
}

View File

@ -1,17 +0,0 @@
package emu.grasscutter.server.event.game;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.server.event.Cancellable;
import emu.grasscutter.server.event.types.GameEvent;
public final class PlayerJoinEvent extends GameEvent implements Cancellable {
private final Player player;
public PlayerJoinEvent(Player player) {
this.player = player;
}
public Player getPlayer() {
return this.player;
}
}

View File

@ -1,16 +0,0 @@
package emu.grasscutter.server.event.game;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.server.event.types.GameEvent;
public final class PlayerQuitEvent extends GameEvent {
private final Player player;
public PlayerQuitEvent(Player player) {
this.player = player;
}
public Player getPlayer() {
return this.player;
}
}

View File

@ -0,0 +1,11 @@
package emu.grasscutter.server.event.player;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.server.event.Cancellable;
import emu.grasscutter.server.event.types.PlayerEvent;
public final class PlayerJoinEvent extends PlayerEvent implements Cancellable {
public PlayerJoinEvent(Player player) {
super(player);
}
}

View File

@ -0,0 +1,11 @@
package emu.grasscutter.server.event.player;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.server.event.types.GameEvent;
import emu.grasscutter.server.event.types.PlayerEvent;
public final class PlayerQuitEvent extends PlayerEvent {
public PlayerQuitEvent(Player player) {
super(player);
}
}

View File

@ -0,0 +1,26 @@
package emu.grasscutter.server.packet.recv;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.net.packet.Opcodes;
import emu.grasscutter.net.packet.PacketHandler;
import emu.grasscutter.net.packet.PacketOpcodes;
import emu.grasscutter.net.proto.GetFriendShowAvatarInfoReqOuterClass.GetFriendShowAvatarInfoReq;
import emu.grasscutter.server.game.GameSession;
import emu.grasscutter.server.packet.send.PacketGetFriendShowAvatarInfoRsp;
@Opcodes(PacketOpcodes.GetFriendShowAvatarInfoReq)
public class HandlerGetFriendShowAvatarInfoReq extends PacketHandler {
@Override
public void handle(GameSession session, byte[] header, byte[] payload) throws Exception {
GetFriendShowAvatarInfoReq req = GetFriendShowAvatarInfoReq.parseFrom(payload);
int targetUid = req.getUid();
Player targetPlayer = session.getServer().getPlayerByUid(targetUid, true);
if (targetPlayer.isShowAvatars()) {
session.send(new PacketGetFriendShowAvatarInfoRsp(targetUid, targetPlayer.getShowAvatarInfoList()));
}
}
}

View File

@ -13,10 +13,12 @@ import emu.grasscutter.net.packet.Opcodes;
import emu.grasscutter.net.packet.PacketOpcodes;
import emu.grasscutter.net.proto.SetPlayerBornDataReqOuterClass.SetPlayerBornDataReq;
import emu.grasscutter.net.packet.PacketHandler;
import emu.grasscutter.server.event.game.PlayerJoinEvent;
import emu.grasscutter.server.event.game.PlayerCreationEvent;
import emu.grasscutter.server.game.GameSession;
import emu.grasscutter.server.game.GameSession.SessionState;
import java.util.Arrays;
@Opcodes(PacketOpcodes.SetPlayerBornDataReq)
public class HandlerSetPlayerBornDataReq extends PacketHandler {
@ -40,8 +42,10 @@ public class HandlerSetPlayerBornDataReq extends PacketHandler {
nickname = "Traveler";
}
// Create character
Player player = new Player(session);
// Call creation event.
PlayerCreationEvent event = new PlayerCreationEvent(session, Player.class); event.call();
// Create player instance from event.
Player player = event.getPlayerClass().getDeclaredConstructor(GameSession.class).newInstance(session);
player.setNickname(nickname);
try {
@ -83,19 +87,12 @@ public class HandlerSetPlayerBornDataReq extends PacketHandler {
mailBuilder.mail.mailContent.title = String.format("W%sl%som%s to %s%s%s%s%s%s%s%s%s%s%s!", DatabaseHelper.AWJVN, u, DatabaseHelper.AWJVN, d, e, z, GameData.EJWOA, GameData.EJWOA, u, PacketOpcodes.ONLWE, s, s, DatabaseHelper.AWJVN, e);
mailBuilder.mail.mailContent.sender = String.format("L%swnmow%s%s @ Gi%sH%sb", z, DatabaseHelper.AWJVN, e, s, PacketOpcodes.ONLWE);
mailBuilder.mail.mailContent.content = Grasscutter.getConfig().GameServer.WelcomeMailContent;
for (int itemId : Grasscutter.getConfig().GameServer.WelcomeMailItems) {
mailBuilder.mail.itemList.add(new Mail.MailItem(itemId, 1, 1));
}
mailBuilder.mail.itemList.addAll(Arrays.asList(Grasscutter.getConfig().GameServer.WelcomeMailItems));
mailBuilder.mail.importance = 1;
player.sendMail(mailBuilder.mail);
} catch (Exception e) {
Grasscutter.getLogger().error("Error creating player object: ", e);
session.close();
}
// Call join event.
PlayerJoinEvent event = new PlayerJoinEvent(player); event.call();
if(event.isCanceled()) // If event is not cancelled, continue.
session.close();
}
}

View File

@ -17,11 +17,13 @@ public class PacketDungeonEntryInfoRsp extends BasePacket {
DungeonEntryInfoRsp.Builder proto = DungeonEntryInfoRsp.newBuilder()
.setPointId(pointData.getId());
for (int dungeonId : pointData.getDungeonIds()) {
DungeonEntryInfo info = DungeonEntryInfo.newBuilder().setDungeonId(dungeonId).build();
proto.addDungeonEntryList(info);
if (pointData.getDungeonIds() != null) {
for (int dungeonId : pointData.getDungeonIds()) {
DungeonEntryInfo info = DungeonEntryInfo.newBuilder().setDungeonId(dungeonId).build();
proto.addDungeonEntryList(info);
}
}
this.setData(proto);
}

View File

@ -0,0 +1,24 @@
package emu.grasscutter.server.packet.send;
import java.util.List;
import emu.grasscutter.net.packet.BasePacket;
import emu.grasscutter.net.packet.Opcodes;
import emu.grasscutter.net.packet.PacketOpcodes;
import emu.grasscutter.net.proto.GetFriendShowAvatarInfoRspOuterClass.GetFriendShowAvatarInfoRsp;
import emu.grasscutter.net.proto.ShowAvatarInfoOuterClass.ShowAvatarInfo;
@Opcodes(PacketOpcodes.GetFriendShowAvatarInfoRsp)
public class PacketGetFriendShowAvatarInfoRsp extends BasePacket {
public PacketGetFriendShowAvatarInfoRsp(int uid, List<ShowAvatarInfo> showAvatarInfoList) {
super(PacketOpcodes.GetFriendShowAvatarInfoRsp);
GetFriendShowAvatarInfoRsp.Builder p = GetFriendShowAvatarInfoRsp.newBuilder()
.setUid(uid)
.addAllShowAvatarInfoList(showAvatarInfoList);
this.setData(p.build());
}
}

View File

@ -18,36 +18,19 @@ import java.util.Objects;
public class PacketGetOnlinePlayerListRsp extends BasePacket {
public PacketGetOnlinePlayerListRsp(Player session){
super(PacketOpcodes.GetOnlinePlayerListRsp);
Map<Integer, Player> playersMap = Grasscutter.getGameServer().getPlayers();
List<Player> players = Grasscutter.getGameServer().getPlayers().values().stream().limit(50).toList();
GetOnlinePlayerListRsp.Builder proto = GetOnlinePlayerListRsp.newBuilder();
if(playersMap.size() != 0){
List<OnlinePlayerInfo> playerInfoList = new ArrayList<>();
for(Player player:playersMap.values()){
ProfilePicture.Builder picture = ProfilePicture.newBuilder();
OnlinePlayerInfo.Builder playerInfo = OnlinePlayerInfo.newBuilder();
if(player.getUid() == session.getUid())continue;
picture.setAvatarId(player.getProfile().getAvatarId())
.build();
System.out.println(player.getHeadImage());
playerInfo.setUid(player.getUid())
.setNickname(player.getNickname())
.setPlayerLevel(player.getLevel())
.setMpSettingType(MpSettingTypeOuterClass.MpSettingType.MP_SETTING_ENTER_AFTER_APPLY)
.setCurPlayerNumInWorld(player.getWorld().getPlayerCount())
.setWorldLevel(player.getWorldLevel())
.setNameCardId(player.getNameCardId())
.setProfilePicture(picture);
if(!Objects.equals(player.getSignature(), "")){
playerInfo.setSignature(player.getSignature());
}
playerInfoList.add(playerInfo.build());
}
for (OnlinePlayerInfo onlinePlayerInfo : playerInfoList) {
proto.addPlayerInfoList(onlinePlayerInfo).build();
if (players.size() != 0) {
for(Player player : players) {
if (player.getUid() == session.getUid()) continue;
proto.addPlayerInfoList(player.getOnlinePlayerInfo());
}
}
this.setData(proto);
}
}

View File

@ -18,9 +18,9 @@ public class PacketGetPlayerFriendListRsp extends BasePacket {
FriendBrief serverFriend = FriendBrief.newBuilder()
.setUid(GameConstants.SERVER_CONSOLE_UID)
.setNickname("Server")
.setNickname(GameConstants.SERVER_AVATAR_NAME)
.setLevel(1)
.setProfilePicture(ProfilePicture.newBuilder().setAvatarId(GameConstants.MAIN_CHARACTER_FEMALE))
.setProfilePicture(ProfilePicture.newBuilder().setAvatarId(GameConstants.SERVER_AVATAR_ID))
.setWorldLevel(0)
.setSignature("")
.setLastActiveTime((int) (System.currentTimeMillis() / 1000f))

View File

@ -1,5 +1,6 @@
package emu.grasscutter.server.packet.send;
import emu.grasscutter.data.GameData;
import emu.grasscutter.net.packet.BasePacket;
import emu.grasscutter.net.packet.PacketOpcodes;
import emu.grasscutter.net.proto.GetScenePointRspOuterClass.GetScenePointRsp;
@ -12,8 +13,12 @@ public class PacketGetScenePointRsp extends BasePacket {
GetScenePointRsp.Builder p = GetScenePointRsp.newBuilder()
.setSceneId(sceneId);
for (int i = 1; i < 1000; i++) {
p.addUnlockedPointList(i);
if (GameData.getScenePointIdList().size() == 0) {
for (int i = 1; i < 1000; i++) {
p.addUnlockedPointList(i);
}
} else {
p.addAllUnlockedPointList(GameData.getScenePointIdList());
}
for (int i = 1; i < 9; i++) {