mirror of
https://github.com/Grasscutters/Grasscutter.git
synced 2025-07-04 05:53:42 +00:00
Compare commits
14 Commits
hyper-opti
...
v1.7.2
Author | SHA1 | Date | |
---|---|---|---|
b6e7d69949 | |||
5faf39d359 | |||
0dd95450b1 | |||
0f0e7aca68 | |||
5ee4812ac5 | |||
ec2bfffdd1 | |||
7f5059cb8f | |||
43db7eba8f | |||
ff6a51db30 | |||
047feaf4aa | |||
88315ec712 | |||
fdad4218e7 | |||
5f5e6c38b1 | |||
92bd09eeed |
@ -68,7 +68,7 @@ class MlgmXyysd_Animation_Company_Proxy:
|
|||||||
]
|
]
|
||||||
|
|
||||||
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.pretty_host in self.LIST_DOMAINS:
|
||||||
if USE_SSL:
|
if USE_SSL:
|
||||||
flow.request.scheme = "https"
|
flow.request.scheme = "https"
|
||||||
else:
|
else:
|
||||||
|
@ -1,5 +1,8 @@
|
|||||||
package emu.grasscutter;
|
package emu.grasscutter;
|
||||||
|
|
||||||
|
import static emu.grasscutter.config.Configuration.SERVER;
|
||||||
|
import static emu.grasscutter.utils.lang.Language.translate;
|
||||||
|
|
||||||
import ch.qos.logback.classic.*;
|
import ch.qos.logback.classic.*;
|
||||||
import emu.grasscutter.auth.*;
|
import emu.grasscutter.auth.*;
|
||||||
import emu.grasscutter.command.*;
|
import emu.grasscutter.command.*;
|
||||||
@ -18,20 +21,16 @@ import emu.grasscutter.tools.Tools;
|
|||||||
import emu.grasscutter.utils.*;
|
import emu.grasscutter.utils.*;
|
||||||
import emu.grasscutter.utils.lang.Language;
|
import emu.grasscutter.utils.lang.Language;
|
||||||
import io.netty.util.concurrent.FastThreadLocalThread;
|
import io.netty.util.concurrent.FastThreadLocalThread;
|
||||||
|
import java.io.*;
|
||||||
|
import java.util.Calendar;
|
||||||
|
import java.util.concurrent.*;
|
||||||
|
import javax.annotation.Nullable;
|
||||||
import lombok.*;
|
import lombok.*;
|
||||||
import org.jline.reader.*;
|
import org.jline.reader.*;
|
||||||
import org.jline.terminal.*;
|
import org.jline.terminal.*;
|
||||||
import org.reflections.Reflections;
|
import org.reflections.Reflections;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
|
||||||
import java.io.*;
|
|
||||||
import java.util.Calendar;
|
|
||||||
import java.util.concurrent.*;
|
|
||||||
|
|
||||||
import static emu.grasscutter.config.Configuration.SERVER;
|
|
||||||
import static emu.grasscutter.utils.lang.Language.translate;
|
|
||||||
|
|
||||||
public final class Grasscutter {
|
public final class Grasscutter {
|
||||||
public static final File configFile = new File("./config.json");
|
public static final File configFile = new File("./config.json");
|
||||||
public static final Reflections reflector = new Reflections("emu.grasscutter");
|
public static final Reflections reflector = new Reflections("emu.grasscutter");
|
||||||
@ -159,6 +158,8 @@ public final class Grasscutter {
|
|||||||
|
|
||||||
// Generate handbooks.
|
// Generate handbooks.
|
||||||
Tools.createGmHandbooks(false);
|
Tools.createGmHandbooks(false);
|
||||||
|
// Generate gacha mappings.
|
||||||
|
Tools.generateGachaMappings();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start servers.
|
// Start servers.
|
||||||
@ -182,18 +183,12 @@ public final class Grasscutter {
|
|||||||
// Hook into shutdown event.
|
// Hook into shutdown event.
|
||||||
Runtime.getRuntime().addShutdownHook(new Thread(Grasscutter::onShutdown));
|
Runtime.getRuntime().addShutdownHook(new Thread(Grasscutter::onShutdown));
|
||||||
|
|
||||||
// Start database heartbeat.
|
|
||||||
Database.startSaveThread();
|
|
||||||
|
|
||||||
// Open console.
|
// Open console.
|
||||||
Grasscutter.startConsole();
|
Grasscutter.startConsole();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Server shutdown event. */
|
/** Server shutdown event. */
|
||||||
private static void onShutdown() {
|
private static void onShutdown() {
|
||||||
// Save all data.
|
|
||||||
Database.saveAll();
|
|
||||||
|
|
||||||
// Disable all plugins.
|
// Disable all plugins.
|
||||||
if (pluginManager != null) pluginManager.disablePlugins();
|
if (pluginManager != null) pluginManager.disablePlugins();
|
||||||
// Shutdown the game server.
|
// Shutdown the game server.
|
||||||
@ -203,14 +198,14 @@ public final class Grasscutter {
|
|||||||
// Wait for Grasscutter's thread pool to finish.
|
// Wait for Grasscutter's thread pool to finish.
|
||||||
var executor = Grasscutter.getThreadPool();
|
var executor = Grasscutter.getThreadPool();
|
||||||
executor.shutdown();
|
executor.shutdown();
|
||||||
if (!executor.awaitTermination(1, TimeUnit.MINUTES)) {
|
if (!executor.awaitTermination(5, TimeUnit.SECONDS)) {
|
||||||
executor.shutdownNow();
|
executor.shutdownNow();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wait for database operations to finish.
|
// Wait for database operations to finish.
|
||||||
var dbExecutor = DatabaseHelper.getEventExecutor();
|
var dbExecutor = DatabaseHelper.getEventExecutor();
|
||||||
dbExecutor.shutdown();
|
dbExecutor.shutdown();
|
||||||
if (!dbExecutor.awaitTermination(2, TimeUnit.MINUTES)) {
|
if (!dbExecutor.awaitTermination(5, TimeUnit.SECONDS)) {
|
||||||
dbExecutor.shutdownNow();
|
dbExecutor.shutdownNow();
|
||||||
}
|
}
|
||||||
} catch (InterruptedException ignored) {
|
} catch (InterruptedException ignored) {
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package emu.grasscutter.command;
|
package emu.grasscutter.command;
|
||||||
|
|
||||||
|
import emu.grasscutter.game.world.Position;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.function.BiConsumer;
|
import java.util.function.BiConsumer;
|
||||||
import java.util.regex.*;
|
import java.util.regex.*;
|
||||||
@ -54,4 +55,78 @@ public class CommandHelpers {
|
|||||||
});
|
});
|
||||||
return args;
|
return args;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static float parseRelative(String input, Float current) {
|
||||||
|
if (input.contains("~")) { // Relative
|
||||||
|
if (!input.equals("~")) { // Relative with offset
|
||||||
|
current += Float.parseFloat(input.replace("~", ""));
|
||||||
|
} // Else no offset, no modification
|
||||||
|
} else { // Absolute
|
||||||
|
current = Float.parseFloat(input);
|
||||||
|
}
|
||||||
|
return current;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Position parsePosition(
|
||||||
|
String inputX, String inputY, String inputZ, Position curPos, Position curRot) {
|
||||||
|
Position offset = new Position();
|
||||||
|
Position target = new Position(curPos);
|
||||||
|
if (inputX.contains("~")) { // Relative
|
||||||
|
if (!inputX.equals("~")) { // Relative with offset
|
||||||
|
target.addX(Float.parseFloat(inputX.replace("~", "")));
|
||||||
|
}
|
||||||
|
} else if (inputX.contains("^")) {
|
||||||
|
if (!inputX.equals("^")) {
|
||||||
|
offset.setX(Float.parseFloat(inputX.replace("^", "")));
|
||||||
|
}
|
||||||
|
} else { // Absolute
|
||||||
|
target.setX(Float.parseFloat(inputX));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (inputY.contains("~")) { // Relative
|
||||||
|
if (!inputY.equals("~")) { // Relative with offset
|
||||||
|
target.addY(Float.parseFloat(inputY.replace("~", "")));
|
||||||
|
}
|
||||||
|
} else if (inputY.contains("^")) {
|
||||||
|
if (!inputY.equals("^")) {
|
||||||
|
offset.setY(Float.parseFloat(inputY.replace("^", "")));
|
||||||
|
}
|
||||||
|
} else { // Absolute
|
||||||
|
target.setY(Float.parseFloat(inputY));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (inputZ.contains("~")) { // Relative
|
||||||
|
if (!inputZ.equals("~")) { // Relative with offset
|
||||||
|
target.addZ(Float.parseFloat(inputZ.replace("~", "")));
|
||||||
|
}
|
||||||
|
} else if (inputZ.contains("^")) {
|
||||||
|
if (!inputZ.equals("^")) {
|
||||||
|
offset.setZ(Float.parseFloat(inputZ.replace("^", "")));
|
||||||
|
}
|
||||||
|
} else { // Absolute
|
||||||
|
target.setZ(Float.parseFloat(inputZ));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!offset.equal3d(Position.ZERO)) {
|
||||||
|
return calculateOffset(target, curRot, offset);
|
||||||
|
} else {
|
||||||
|
return target;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Position calculateOffset(Position pos, Position rot, Position offset) {
|
||||||
|
// Degrees to radians
|
||||||
|
float angleZ = (float) Math.toRadians(rot.getY());
|
||||||
|
float angleX = (float) Math.toRadians(rot.getY() + 90);
|
||||||
|
|
||||||
|
// Calculate offset based on current position and rotation
|
||||||
|
return new Position(
|
||||||
|
pos.getX()
|
||||||
|
+ offset.getZ() * (float) Math.sin(angleZ)
|
||||||
|
+ offset.getX() * (float) Math.sin(angleX),
|
||||||
|
pos.getY() + offset.getY(),
|
||||||
|
pos.getZ()
|
||||||
|
+ offset.getZ() * (float) Math.cos(angleZ)
|
||||||
|
+ offset.getX() * (float) Math.cos(angleX));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -21,9 +21,9 @@ import lombok.Setter;
|
|||||||
label = "spawn",
|
label = "spawn",
|
||||||
aliases = {"drop", "s"},
|
aliases = {"drop", "s"},
|
||||||
usage = {
|
usage = {
|
||||||
"<itemId> [x<amount>] [blk<blockId>] [grp<groupId>] [cfg<configId>] <x> <y> <z>",
|
"<itemId> [x<amount>] [blk<blockId>] [grp<groupId>] [cfg<configId>] [<x> <y> <z>] [<rotX> <rotY> <rotZ>]",
|
||||||
"<gadgetId> [x<amount>] [state<state>] [maxhp<maxhp>] [hp<hp>(0 for infinite)] [atk<atk>] [def<def>] [blk<blockId>] [grp<groupId>] [cfg<configId>] <x> <y> <z>",
|
"<gadgetId> [x<amount>] [state<state>] [maxhp<maxhp>] [hp<hp>(0 for infinite)] [atk<atk>] [def<def>] [blk<blockId>] [grp<groupId>] [cfg<configId>] [<x> <y> <z>] [<rotX> <rotY> <rotZ>]",
|
||||||
"<monsterId> [x<amount>] [lv<level>] [ai<aiId>] [maxhp<maxhp>] [hp<hp>(0 for infinite)] [atk<atk>] [def<def>] [blk<blockId>] [grp<groupId>] [cfg<configId>] <x> <y> <z>"
|
"<monsterId> [x<amount>] [lv<level>] [ai<aiId>] [maxhp<maxhp>] [hp<hp>(0 for infinite)] [atk<atk>] [def<def>] [blk<blockId>] [grp<groupId>] [cfg<configId>] [<x> <y> <z>] [<rotX> <rotY> <rotZ>]"
|
||||||
},
|
},
|
||||||
permission = "server.spawn",
|
permission = "server.spawn",
|
||||||
permissionTargeted = "server.spawn.others")
|
permissionTargeted = "server.spawn.others")
|
||||||
@ -53,14 +53,23 @@ public final class SpawnCommand implements CommandHandler {
|
|||||||
sendUsageMessage(sender); // Reachable if someone does `/give lv90` or similar
|
sendUsageMessage(sender); // Reachable if someone does `/give lv90` or similar
|
||||||
throw new IllegalArgumentException();
|
throw new IllegalArgumentException();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Position pos = new Position(targetPlayer.getPosition());
|
||||||
|
Position rot = new Position(targetPlayer.getRotation());
|
||||||
|
|
||||||
switch (args.size()) {
|
switch (args.size()) {
|
||||||
|
case 7:
|
||||||
|
try {
|
||||||
|
rot.setX(CommandHelpers.parseRelative(args.get(4), rot.getX()));
|
||||||
|
rot.setY(CommandHelpers.parseRelative(args.get(5), rot.getY()));
|
||||||
|
rot.setZ(CommandHelpers.parseRelative(args.get(6), rot.getZ()));
|
||||||
|
} catch (NumberFormatException ignored) {
|
||||||
|
CommandHandler.sendMessage(
|
||||||
|
sender, translate(sender, "commands.execution.argument_error"));
|
||||||
|
} // Fallthrough
|
||||||
case 4:
|
case 4:
|
||||||
try {
|
try {
|
||||||
float x, y, z;
|
pos = CommandHelpers.parsePosition(args.get(1), args.get(2), args.get(3), pos, rot);
|
||||||
x = Float.parseFloat(args.get(1));
|
|
||||||
y = Float.parseFloat(args.get(2));
|
|
||||||
z = Float.parseFloat(args.get(3));
|
|
||||||
param.pos = new Position(x, y, z);
|
|
||||||
} catch (NumberFormatException ignored) {
|
} catch (NumberFormatException ignored) {
|
||||||
CommandHandler.sendMessage(
|
CommandHandler.sendMessage(
|
||||||
sender, translate(sender, "commands.execution.argument_error"));
|
sender, translate(sender, "commands.execution.argument_error"));
|
||||||
@ -77,6 +86,8 @@ public final class SpawnCommand implements CommandHandler {
|
|||||||
sendUsageMessage(sender);
|
sendUsageMessage(sender);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
param.pos = pos;
|
||||||
|
param.rot = rot;
|
||||||
|
|
||||||
MonsterData monsterData = GameData.getMonsterDataMap().get(param.id);
|
MonsterData monsterData = GameData.getMonsterDataMap().get(param.id);
|
||||||
GadgetData gadgetData = GameData.getGadgetDataMap().get(param.id);
|
GadgetData gadgetData = GameData.getGadgetDataMap().get(param.id);
|
||||||
@ -102,12 +113,8 @@ public final class SpawnCommand implements CommandHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
double maxRadius = Math.sqrt(param.amount * 0.2 / Math.PI);
|
double maxRadius = Math.sqrt(param.amount * 0.2 / Math.PI);
|
||||||
if (param.pos == null) {
|
|
||||||
param.pos = targetPlayer.getPosition();
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int i = 0; i < param.amount; i++) {
|
for (int i = 0; i < param.amount; i++) {
|
||||||
Position pos = GetRandomPositionInCircle(param.pos, maxRadius).addY(3);
|
pos = GetRandomPositionInCircle(param.pos, maxRadius).addY(3);
|
||||||
GameEntity entity = null;
|
GameEntity entity = null;
|
||||||
if (itemData != null) {
|
if (itemData != null) {
|
||||||
entity = createItem(itemData, param, pos);
|
entity = createItem(itemData, param, pos);
|
||||||
@ -128,12 +135,12 @@ public final class SpawnCommand implements CommandHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private EntityItem createItem(ItemData itemData, SpawnParameters param, Position pos) {
|
private EntityItem createItem(ItemData itemData, SpawnParameters param, Position pos) {
|
||||||
return new EntityItem(param.scene, null, itemData, pos, 1, true);
|
return new EntityItem(param.scene, null, itemData, pos, param.rot, 1, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
private EntityMonster createMonster(
|
private EntityMonster createMonster(
|
||||||
MonsterData monsterData, SpawnParameters param, Position pos) {
|
MonsterData monsterData, SpawnParameters param, Position pos) {
|
||||||
var entity = new EntityMonster(param.scene, monsterData, pos, param.lvl);
|
var entity = new EntityMonster(param.scene, monsterData, pos, param.rot, param.lvl);
|
||||||
if (param.ai != -1) {
|
if (param.ai != -1) {
|
||||||
entity.setAiId(param.ai);
|
entity.setAiId(param.ai);
|
||||||
}
|
}
|
||||||
@ -144,16 +151,13 @@ public final class SpawnCommand implements CommandHandler {
|
|||||||
GadgetData gadgetData, SpawnParameters param, Position pos, Player targetPlayer) {
|
GadgetData gadgetData, SpawnParameters param, Position pos, Player targetPlayer) {
|
||||||
EntityBaseGadget entity;
|
EntityBaseGadget entity;
|
||||||
if (gadgetData.getType() == EntityType.Vehicle) {
|
if (gadgetData.getType() == EntityType.Vehicle) {
|
||||||
entity =
|
entity = new EntityVehicle(param.scene, targetPlayer, param.id, 0, pos, param.rot);
|
||||||
new EntityVehicle(
|
|
||||||
param.scene, targetPlayer, param.id, 0, pos, targetPlayer.getRotation());
|
|
||||||
} else {
|
} else {
|
||||||
entity = new EntityGadget(param.scene, param.id, pos, targetPlayer.getRotation());
|
entity = new EntityGadget(param.scene, param.id, pos, param.rot);
|
||||||
if (param.state != -1) {
|
if (param.state != -1) {
|
||||||
((EntityGadget) entity).setState(param.state);
|
((EntityGadget) entity).setState(param.state);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return entity;
|
return entity;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -207,6 +211,7 @@ public final class SpawnCommand implements CommandHandler {
|
|||||||
@Setter public int def = -1;
|
@Setter public int def = -1;
|
||||||
@Setter public int ai = -1;
|
@Setter public int ai = -1;
|
||||||
@Setter public Position pos = null;
|
@Setter public Position pos = null;
|
||||||
|
@Setter public Position rot = null;
|
||||||
public Scene scene = null;
|
public Scene scene = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -16,24 +16,10 @@ import java.util.List;
|
|||||||
permissionTargeted = "player.teleport.others")
|
permissionTargeted = "player.teleport.others")
|
||||||
public final class TeleportCommand implements CommandHandler {
|
public final class TeleportCommand implements CommandHandler {
|
||||||
|
|
||||||
private float parseRelative(
|
|
||||||
String input, Float current) { // TODO: Maybe this will be useful elsewhere later
|
|
||||||
if (input.contains("~")) { // Relative
|
|
||||||
if (!input.equals("~")) { // Relative with offset
|
|
||||||
current += Float.parseFloat(input.replace("~", ""));
|
|
||||||
} // Else no offset, no modification
|
|
||||||
} else { // Absolute
|
|
||||||
current = Float.parseFloat(input);
|
|
||||||
}
|
|
||||||
return current;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void execute(Player sender, Player targetPlayer, List<String> args) {
|
public void execute(Player sender, Player targetPlayer, List<String> args) {
|
||||||
Position pos = targetPlayer.getPosition();
|
Position pos = new Position(targetPlayer.getPosition());
|
||||||
float x = pos.getX();
|
Position rot = new Position(targetPlayer.getRotation());
|
||||||
float y = pos.getY();
|
|
||||||
float z = pos.getZ();
|
|
||||||
int sceneId = targetPlayer.getSceneId();
|
int sceneId = targetPlayer.getSceneId();
|
||||||
|
|
||||||
switch (args.size()) {
|
switch (args.size()) {
|
||||||
@ -46,9 +32,7 @@ public final class TeleportCommand implements CommandHandler {
|
|||||||
} // Fallthrough
|
} // Fallthrough
|
||||||
case 3:
|
case 3:
|
||||||
try {
|
try {
|
||||||
x = this.parseRelative(args.get(0), x);
|
pos = CommandHelpers.parsePosition(args.get(0), args.get(1), args.get(2), pos, rot);
|
||||||
y = this.parseRelative(args.get(1), y);
|
|
||||||
z = this.parseRelative(args.get(2), z);
|
|
||||||
} catch (NumberFormatException ignored) {
|
} catch (NumberFormatException ignored) {
|
||||||
CommandHandler.sendMessage(
|
CommandHandler.sendMessage(
|
||||||
sender, translate(sender, "commands.teleport.invalid_position"));
|
sender, translate(sender, "commands.teleport.invalid_position"));
|
||||||
@ -59,11 +43,10 @@ public final class TeleportCommand implements CommandHandler {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Position target_pos = new Position(x, y, z);
|
|
||||||
boolean result =
|
boolean result =
|
||||||
targetPlayer
|
targetPlayer
|
||||||
.getWorld()
|
.getWorld()
|
||||||
.transferPlayerToScene(targetPlayer, sceneId, TeleportType.COMMAND, target_pos);
|
.transferPlayerToScene(targetPlayer, sceneId, TeleportType.COMMAND, pos);
|
||||||
|
|
||||||
if (!result) {
|
if (!result) {
|
||||||
CommandHandler.sendMessage(sender, translate(sender, "commands.teleport.exists_error"));
|
CommandHandler.sendMessage(sender, translate(sender, "commands.teleport.exists_error"));
|
||||||
@ -71,7 +54,13 @@ public final class TeleportCommand implements CommandHandler {
|
|||||||
CommandHandler.sendMessage(
|
CommandHandler.sendMessage(
|
||||||
sender,
|
sender,
|
||||||
translate(
|
translate(
|
||||||
sender, "commands.teleport.success", targetPlayer.getNickname(), x, y, z, sceneId));
|
sender,
|
||||||
|
"commands.teleport.success",
|
||||||
|
targetPlayer.getNickname(),
|
||||||
|
pos.getX(),
|
||||||
|
pos.getY(),
|
||||||
|
pos.getZ(),
|
||||||
|
sceneId));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
package emu.grasscutter.data;
|
package emu.grasscutter.data;
|
||||||
|
|
||||||
import emu.grasscutter.Grasscutter;
|
import emu.grasscutter.Grasscutter;
|
||||||
import emu.grasscutter.server.http.handlers.GachaHandler;
|
|
||||||
import emu.grasscutter.tools.Tools;
|
|
||||||
import emu.grasscutter.utils.*;
|
import emu.grasscutter.utils.*;
|
||||||
import java.io.*;
|
import java.io.*;
|
||||||
import java.nio.file.*;
|
import java.nio.file.*;
|
||||||
@ -114,8 +112,6 @@ public class DataLoader {
|
|||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
Grasscutter.getLogger().error("An error occurred while trying to check the data folder.", e);
|
Grasscutter.getLogger().error("An error occurred while trying to check the data folder.", e);
|
||||||
}
|
}
|
||||||
|
|
||||||
generateGachaMappings();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void checkAndCopyData(String name) {
|
private static void checkAndCopyData(String name) {
|
||||||
@ -131,16 +127,4 @@ public class DataLoader {
|
|||||||
FileUtils.copyResource("/defaults/data/" + name, filePath.toString());
|
FileUtils.copyResource("/defaults/data/" + name, filePath.toString());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void generateGachaMappings() {
|
|
||||||
var path = GachaHandler.getGachaMappingsPath();
|
|
||||||
if (!Files.exists(path)) {
|
|
||||||
try {
|
|
||||||
Grasscutter.getLogger().debug("Creating default '" + path + "' data");
|
|
||||||
Tools.createGachaMappings(path);
|
|
||||||
} catch (Exception exception) {
|
|
||||||
Grasscutter.getLogger().warn("Failed to create gacha mappings. \n" + exception);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -214,6 +214,14 @@ public final class GameData {
|
|||||||
private static final Int2ObjectMap<CookRecipeData> cookRecipeDataMap =
|
private static final Int2ObjectMap<CookRecipeData> cookRecipeDataMap =
|
||||||
new Int2ObjectOpenHashMap<>();
|
new Int2ObjectOpenHashMap<>();
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
private static final Int2ObjectMap<CoopChapterData> coopChapterDataMap =
|
||||||
|
new Int2ObjectOpenHashMap<>();
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
private static final Int2ObjectMap<CoopPointData> coopPointDataMap =
|
||||||
|
new Int2ObjectOpenHashMap<>();
|
||||||
|
|
||||||
@Getter
|
@Getter
|
||||||
private static final Int2ObjectMap<CompoundData> compoundDataMap = new Int2ObjectOpenHashMap<>();
|
private static final Int2ObjectMap<CompoundData> compoundDataMap = new Int2ObjectOpenHashMap<>();
|
||||||
|
|
||||||
|
@ -0,0 +1,46 @@
|
|||||||
|
package emu.grasscutter.data.excels;
|
||||||
|
|
||||||
|
import com.google.gson.annotations.SerializedName;
|
||||||
|
import emu.grasscutter.data.*;
|
||||||
|
import java.util.List;
|
||||||
|
import lombok.*;
|
||||||
|
import lombok.experimental.FieldDefaults;
|
||||||
|
|
||||||
|
@ResourceType(name = "CoopChapterExcelConfigData.json")
|
||||||
|
@Getter
|
||||||
|
@Setter // TODO: remove setters next API break
|
||||||
|
@FieldDefaults(level = AccessLevel.PRIVATE)
|
||||||
|
public class CoopChapterData extends GameResource {
|
||||||
|
@Getter(onMethod_ = @Override)
|
||||||
|
int id;
|
||||||
|
|
||||||
|
int avatarId;
|
||||||
|
// int chapterNameTextMapHash;
|
||||||
|
// int coopPageTitleTextMapHash;
|
||||||
|
// int chapterSortId;
|
||||||
|
// int avatarSortId;
|
||||||
|
// String chapterIcon;
|
||||||
|
List<CoopCondition> unlockCond;
|
||||||
|
// int [] unlockCondTips;
|
||||||
|
// int openMaterialId;
|
||||||
|
// int openMaterialNum;
|
||||||
|
// String beginTimeStr;
|
||||||
|
// int confidenceValue;
|
||||||
|
// String pointGraphPath;
|
||||||
|
// Double graphXRatio;
|
||||||
|
// Double graphYRatio;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@FieldDefaults(level = AccessLevel.PRIVATE)
|
||||||
|
private static class CoopCondition {
|
||||||
|
@SerializedName(
|
||||||
|
value = "_condType",
|
||||||
|
alternate = {"condType"})
|
||||||
|
String type = "COOP_COND_NONE";
|
||||||
|
|
||||||
|
@SerializedName(
|
||||||
|
value = "_args",
|
||||||
|
alternate = {"args"})
|
||||||
|
int[] args;
|
||||||
|
}
|
||||||
|
}
|
24
src/main/java/emu/grasscutter/data/excels/CoopPointData.java
Normal file
24
src/main/java/emu/grasscutter/data/excels/CoopPointData.java
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
package emu.grasscutter.data.excels;
|
||||||
|
|
||||||
|
import emu.grasscutter.data.*;
|
||||||
|
import lombok.*;
|
||||||
|
import lombok.experimental.FieldDefaults;
|
||||||
|
|
||||||
|
@ResourceType(name = "CoopPointExcelConfigData.json")
|
||||||
|
@Getter
|
||||||
|
@Setter // TODO: remove setters next API break
|
||||||
|
@FieldDefaults(level = AccessLevel.PRIVATE)
|
||||||
|
public class CoopPointData extends GameResource {
|
||||||
|
@Getter(onMethod_ = @Override)
|
||||||
|
int id;
|
||||||
|
|
||||||
|
int chapterId;
|
||||||
|
String type;
|
||||||
|
int acceptQuest;
|
||||||
|
int[] postPointList;
|
||||||
|
// int pointNameTextMapHash;
|
||||||
|
// int pointDecTextMapHash;
|
||||||
|
int pointPosId;
|
||||||
|
// long photoMaleHash;
|
||||||
|
// long photoFemaleHash;
|
||||||
|
}
|
@ -1,85 +0,0 @@
|
|||||||
package emu.grasscutter.database;
|
|
||||||
|
|
||||||
import emu.grasscutter.Grasscutter;
|
|
||||||
import emu.grasscutter.utils.objects.DatabaseObject;
|
|
||||||
import org.slf4j.*;
|
|
||||||
|
|
||||||
import java.util.*;
|
|
||||||
import java.util.concurrent.*;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Complicated manager of the MongoDB database.
|
|
||||||
* Handles caching, data operations, and more.
|
|
||||||
*/
|
|
||||||
public interface Database {
|
|
||||||
Logger logger = LoggerFactory.getLogger("Database");
|
|
||||||
List<DatabaseObject<?>> objects = new CopyOnWriteArrayList<>();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Queues an object to be saved.
|
|
||||||
*
|
|
||||||
* @param object The object to save.
|
|
||||||
*/
|
|
||||||
static void save(DatabaseObject<?> object) {
|
|
||||||
if (object.saveImmediately()) {
|
|
||||||
object.save();
|
|
||||||
} else {
|
|
||||||
objects.add(object);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Performs a bulk save of all deferred objects.
|
|
||||||
*/
|
|
||||||
static void saveAll() {
|
|
||||||
var size = objects.size();
|
|
||||||
Database.saveAll(objects);
|
|
||||||
|
|
||||||
logger.debug("Performed auto save on {} objects.", size);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Performs a bulk save of all deferred objects.
|
|
||||||
*
|
|
||||||
* @param objects The objects to save.
|
|
||||||
*/
|
|
||||||
static void saveAll(List<? extends DatabaseObject<?>> objects) {
|
|
||||||
// Sort all objects into their respective databases.
|
|
||||||
var gameObjects = objects.stream()
|
|
||||||
.filter(DatabaseObject::isGameObject)
|
|
||||||
.toList();
|
|
||||||
var accountObjects = objects.stream()
|
|
||||||
.filter(o -> !o.isGameObject())
|
|
||||||
.toList();
|
|
||||||
|
|
||||||
// Clear the collective list.
|
|
||||||
objects.clear();
|
|
||||||
|
|
||||||
// Save all objects.
|
|
||||||
var executor = DatabaseHelper.getEventExecutor();
|
|
||||||
if (Grasscutter.getRunMode() != Grasscutter.ServerRunMode.DISPATCH_ONLY) {
|
|
||||||
executor.submit(() -> {
|
|
||||||
DatabaseManager.getGameDatastore().save(gameObjects);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (Grasscutter.getRunMode() != Grasscutter.ServerRunMode.GAME_ONLY) {
|
|
||||||
executor.submit(() -> {
|
|
||||||
DatabaseManager.getAccountDatastore().save(accountObjects);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Starts the auto-save thread.
|
|
||||||
* Runs every 5 minutes.
|
|
||||||
*/
|
|
||||||
static void startSaveThread() {
|
|
||||||
var timer = new Timer();
|
|
||||||
timer.scheduleAtFixedRate(new TimerTask() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
Database.saveAll();
|
|
||||||
}
|
|
||||||
}, 0, 1000 * 60 * 5);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,5 +1,7 @@
|
|||||||
package emu.grasscutter.database;
|
package emu.grasscutter.database;
|
||||||
|
|
||||||
|
import static com.mongodb.client.model.Filters.eq;
|
||||||
|
|
||||||
import dev.morphia.query.*;
|
import dev.morphia.query.*;
|
||||||
import dev.morphia.query.experimental.filters.Filters;
|
import dev.morphia.query.experimental.filters.Filters;
|
||||||
import emu.grasscutter.*;
|
import emu.grasscutter.*;
|
||||||
@ -18,19 +20,24 @@ import emu.grasscutter.game.player.Player;
|
|||||||
import emu.grasscutter.game.quest.GameMainQuest;
|
import emu.grasscutter.game.quest.GameMainQuest;
|
||||||
import emu.grasscutter.game.world.SceneGroupInstance;
|
import emu.grasscutter.game.world.SceneGroupInstance;
|
||||||
import emu.grasscutter.utils.objects.Returnable;
|
import emu.grasscutter.utils.objects.Returnable;
|
||||||
import lombok.Getter;
|
import io.netty.util.concurrent.FastThreadLocalThread;
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.*;
|
import java.util.concurrent.*;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
import javax.annotation.Nullable;
|
||||||
import static com.mongodb.client.model.Filters.eq;
|
import lombok.Getter;
|
||||||
|
|
||||||
public final class DatabaseHelper {
|
public final class DatabaseHelper {
|
||||||
@Getter
|
@Getter
|
||||||
private static final ExecutorService eventExecutor =
|
private static final ExecutorService eventExecutor =
|
||||||
Executors.newFixedThreadPool(4);
|
new ThreadPoolExecutor(
|
||||||
|
6,
|
||||||
|
6,
|
||||||
|
60,
|
||||||
|
TimeUnit.SECONDS,
|
||||||
|
new LinkedBlockingDeque<>(),
|
||||||
|
FastThreadLocalThread::new,
|
||||||
|
new ThreadPoolExecutor.AbortPolicy());
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Saves an object on the account datastore.
|
* Saves an object on the account datastore.
|
||||||
|
@ -241,7 +241,8 @@ public interface HandbookActions {
|
|||||||
|
|
||||||
// Create the entity.
|
// Create the entity.
|
||||||
for (var i = 1; i <= request.getAmount(); i++) {
|
for (var i = 1; i <= request.getAmount(); i++) {
|
||||||
var entity = new EntityMonster(scene, entityData, player.getPosition(), level);
|
var entity =
|
||||||
|
new EntityMonster(scene, entityData, player.getPosition(), player.getRotation(), level);
|
||||||
scene.addEntity(entity);
|
scene.addEntity(entity);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -12,13 +12,12 @@ import emu.grasscutter.game.props.ActionReason;
|
|||||||
import emu.grasscutter.net.proto.AchievementOuterClass.Achievement.Status;
|
import emu.grasscutter.net.proto.AchievementOuterClass.Achievement.Status;
|
||||||
import emu.grasscutter.server.event.player.PlayerCompleteAchievementEvent;
|
import emu.grasscutter.server.event.player.PlayerCompleteAchievementEvent;
|
||||||
import emu.grasscutter.server.packet.send.*;
|
import emu.grasscutter.server.packet.send.*;
|
||||||
import lombok.*;
|
|
||||||
import org.bson.types.ObjectId;
|
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
import java.util.function.IntSupplier;
|
import java.util.function.IntSupplier;
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
import lombok.*;
|
||||||
|
import org.bson.types.ObjectId;
|
||||||
|
|
||||||
@Entity("achievements")
|
@Entity("achievements")
|
||||||
@Data
|
@Data
|
||||||
@ -45,30 +44,15 @@ public class Achievements {
|
|||||||
return achievements;
|
return achievements;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public static Achievements create(int uid) {
|
||||||
* Creates a blank achievements object.
|
var newAchievement =
|
||||||
*
|
Achievements.of()
|
||||||
* @return The achievements object.
|
.uid(uid)
|
||||||
*/
|
|
||||||
public static Achievements blank() {
|
|
||||||
return Achievements.of()
|
|
||||||
.achievementList(init())
|
.achievementList(init())
|
||||||
.finishedAchievementNum(0)
|
.finishedAchievementNum(0)
|
||||||
.takenGoalRewardIdList(Lists.newArrayList())
|
.takenGoalRewardIdList(Lists.newArrayList())
|
||||||
.build();
|
.build();
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates and saves a blank achievements object.
|
|
||||||
*
|
|
||||||
* @param uid The UID of the player.
|
|
||||||
* @return The achievements object.
|
|
||||||
*/
|
|
||||||
public static Achievements create(int uid) {
|
|
||||||
var newAchievement = blank();
|
|
||||||
newAchievement.setUid(uid);
|
|
||||||
newAchievement.save();
|
newAchievement.save();
|
||||||
|
|
||||||
return newAchievement;
|
return newAchievement;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -11,17 +11,15 @@ import emu.grasscutter.game.props.ActionReason;
|
|||||||
import emu.grasscutter.net.proto.ActivityWatcherInfoOuterClass;
|
import emu.grasscutter.net.proto.ActivityWatcherInfoOuterClass;
|
||||||
import emu.grasscutter.server.packet.send.PacketActivityUpdateWatcherNotify;
|
import emu.grasscutter.server.packet.send.PacketActivityUpdateWatcherNotify;
|
||||||
import emu.grasscutter.utils.JsonUtils;
|
import emu.grasscutter.utils.JsonUtils;
|
||||||
import emu.grasscutter.utils.objects.DatabaseObject;
|
import java.util.*;
|
||||||
import lombok.*;
|
import lombok.*;
|
||||||
import lombok.experimental.FieldDefaults;
|
import lombok.experimental.FieldDefaults;
|
||||||
|
|
||||||
import java.util.*;
|
|
||||||
|
|
||||||
@Entity("activities")
|
@Entity("activities")
|
||||||
@Data
|
@Data
|
||||||
@FieldDefaults(level = AccessLevel.PRIVATE)
|
@FieldDefaults(level = AccessLevel.PRIVATE)
|
||||||
@Builder(builderMethodName = "of")
|
@Builder(builderMethodName = "of")
|
||||||
public class PlayerActivityData implements DatabaseObject<PlayerActivityData> {
|
public class PlayerActivityData {
|
||||||
@Id String id;
|
@Id String id;
|
||||||
int uid;
|
int uid;
|
||||||
int activityId;
|
int activityId;
|
||||||
@ -36,25 +34,8 @@ public class PlayerActivityData implements DatabaseObject<PlayerActivityData> {
|
|||||||
return DatabaseHelper.getPlayerActivityData(player.getUid(), activityId);
|
return DatabaseHelper.getPlayerActivityData(player.getUid(), activityId);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Saves this object to the database.
|
|
||||||
* As of Grasscutter 1.7.1, this is by default a {@link DatabaseObject#deferSave()} call.
|
|
||||||
*/
|
|
||||||
public void save() {
|
public void save() {
|
||||||
this.deferSave();
|
DatabaseHelper.savePlayerActivityData(this);
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Saves this object to the database.
|
|
||||||
*
|
|
||||||
* @param immediate If true, this will be a {@link DatabaseObject#save()} call instead of a {@link DatabaseObject#deferSave()} call.
|
|
||||||
*/
|
|
||||||
public void save(boolean immediate) {
|
|
||||||
if (immediate) {
|
|
||||||
DatabaseObject.super.save();
|
|
||||||
} else {
|
|
||||||
this.save();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public synchronized void addWatcherProgress(int watcherId) {
|
public synchronized void addWatcherProgress(int watcherId) {
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
package emu.grasscutter.game.avatar;
|
package emu.grasscutter.game.avatar;
|
||||||
|
|
||||||
|
import static emu.grasscutter.config.Configuration.GAME_OPTIONS;
|
||||||
|
|
||||||
import dev.morphia.annotations.*;
|
import dev.morphia.annotations.*;
|
||||||
import emu.grasscutter.GameConstants;
|
import emu.grasscutter.GameConstants;
|
||||||
import emu.grasscutter.data.GameData;
|
import emu.grasscutter.data.GameData;
|
||||||
@ -13,6 +15,7 @@ import emu.grasscutter.data.excels.avatar.AvatarSkillDepotData.InherentProudSkil
|
|||||||
import emu.grasscutter.data.excels.reliquary.*;
|
import emu.grasscutter.data.excels.reliquary.*;
|
||||||
import emu.grasscutter.data.excels.trial.TrialAvatarTemplateData;
|
import emu.grasscutter.data.excels.trial.TrialAvatarTemplateData;
|
||||||
import emu.grasscutter.data.excels.weapon.*;
|
import emu.grasscutter.data.excels.weapon.*;
|
||||||
|
import emu.grasscutter.database.DatabaseHelper;
|
||||||
import emu.grasscutter.game.entity.*;
|
import emu.grasscutter.game.entity.*;
|
||||||
import emu.grasscutter.game.inventory.*;
|
import emu.grasscutter.game.inventory.*;
|
||||||
import emu.grasscutter.game.player.Player;
|
import emu.grasscutter.game.player.Player;
|
||||||
@ -28,19 +31,15 @@ import emu.grasscutter.net.proto.TrialAvatarGrantRecordOuterClass.TrialAvatarGra
|
|||||||
import emu.grasscutter.net.proto.TrialAvatarInfoOuterClass.TrialAvatarInfo;
|
import emu.grasscutter.net.proto.TrialAvatarInfoOuterClass.TrialAvatarInfo;
|
||||||
import emu.grasscutter.server.packet.send.*;
|
import emu.grasscutter.server.packet.send.*;
|
||||||
import emu.grasscutter.utils.helpers.ProtoHelper;
|
import emu.grasscutter.utils.helpers.ProtoHelper;
|
||||||
import emu.grasscutter.utils.objects.DatabaseObject;
|
|
||||||
import it.unimi.dsi.fastutil.ints.*;
|
import it.unimi.dsi.fastutil.ints.*;
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
import javax.annotation.*;
|
||||||
import lombok.*;
|
import lombok.*;
|
||||||
import org.bson.types.ObjectId;
|
import org.bson.types.ObjectId;
|
||||||
|
|
||||||
import javax.annotation.*;
|
|
||||||
import java.util.*;
|
|
||||||
import java.util.stream.Stream;
|
|
||||||
|
|
||||||
import static emu.grasscutter.config.Configuration.GAME_OPTIONS;
|
|
||||||
|
|
||||||
@Entity(value = "avatars", useDiscriminator = false)
|
@Entity(value = "avatars", useDiscriminator = false)
|
||||||
public class Avatar implements DatabaseObject<Avatar> {
|
public class Avatar {
|
||||||
@Transient @Getter private final Int2ObjectMap<GameItem> equips;
|
@Transient @Getter private final Int2ObjectMap<GameItem> equips;
|
||||||
@Transient @Getter private final Int2FloatOpenHashMap fightProperties;
|
@Transient @Getter private final Int2FloatOpenHashMap fightProperties;
|
||||||
@Transient @Getter private final Int2FloatOpenHashMap fightPropOverrides;
|
@Transient @Getter private final Int2FloatOpenHashMap fightPropOverrides;
|
||||||
@ -990,25 +989,8 @@ public class Avatar implements DatabaseObject<Avatar> {
|
|||||||
return entity != null ? entity.getId() : 0;
|
return entity != null ? entity.getId() : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Saves this object to the database.
|
|
||||||
* As of Grasscutter 1.7.1, this is by default a {@link DatabaseObject#deferSave()} call.
|
|
||||||
*/
|
|
||||||
public void save() {
|
public void save() {
|
||||||
this.deferSave();
|
DatabaseHelper.saveAvatar(this);
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Saves this object to the database.
|
|
||||||
*
|
|
||||||
* @param immediate If true, this will be a {@link DatabaseObject#save()} call instead of a {@link DatabaseObject#deferSave()} call.
|
|
||||||
*/
|
|
||||||
public void save(boolean immediate) {
|
|
||||||
if (immediate) {
|
|
||||||
DatabaseObject.super.save();
|
|
||||||
} else {
|
|
||||||
this.save();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public AvatarInfo toProto() {
|
public AvatarInfo toProto() {
|
||||||
|
@ -60,7 +60,7 @@ public class AvatarStorage extends BasePlayerManager implements Iterable<Avatar>
|
|||||||
this.avatars.put(avatar.getAvatarId(), avatar);
|
this.avatars.put(avatar.getAvatarId(), avatar);
|
||||||
this.avatarsGuid.put(avatar.getGuid(), avatar);
|
this.avatarsGuid.put(avatar.getGuid(), avatar);
|
||||||
|
|
||||||
avatar.save(true);
|
avatar.save();
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -165,7 +165,7 @@ public class AvatarStorage extends BasePlayerManager implements Iterable<Avatar>
|
|||||||
if ((avatar.getAvatarId() == 10000007) || (avatar.getAvatarId() == 10000005)) {
|
if ((avatar.getAvatarId() == 10000007) || (avatar.getAvatarId() == 10000005)) {
|
||||||
avatar.setSkillDepot(skillDepot);
|
avatar.setSkillDepot(skillDepot);
|
||||||
avatar.setSkillDepotData(skillDepot);
|
avatar.setSkillDepotData(skillDepot);
|
||||||
avatar.save(true);
|
avatar.save();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -14,12 +14,11 @@ import emu.grasscutter.net.proto.BattlePassRewardTakeOptionOuterClass.BattlePass
|
|||||||
import emu.grasscutter.net.proto.BattlePassScheduleOuterClass.BattlePassSchedule;
|
import emu.grasscutter.net.proto.BattlePassScheduleOuterClass.BattlePassSchedule;
|
||||||
import emu.grasscutter.net.proto.BattlePassUnlockStatusOuterClass.BattlePassUnlockStatus;
|
import emu.grasscutter.net.proto.BattlePassUnlockStatusOuterClass.BattlePassUnlockStatus;
|
||||||
import emu.grasscutter.server.packet.send.*;
|
import emu.grasscutter.server.packet.send.*;
|
||||||
import lombok.Getter;
|
|
||||||
import org.bson.types.ObjectId;
|
|
||||||
|
|
||||||
import java.time.*;
|
import java.time.*;
|
||||||
import java.time.temporal.TemporalAdjusters;
|
import java.time.temporal.TemporalAdjusters;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
import lombok.Getter;
|
||||||
|
import org.bson.types.ObjectId;
|
||||||
|
|
||||||
@Entity(value = "battlepass", useDiscriminator = false)
|
@Entity(value = "battlepass", useDiscriminator = false)
|
||||||
public class BattlePassManager extends BasePlayerDataManager {
|
public class BattlePassManager extends BasePlayerDataManager {
|
||||||
@ -41,10 +40,7 @@ public class BattlePassManager extends BasePlayerDataManager {
|
|||||||
|
|
||||||
public BattlePassManager(Player player) {
|
public BattlePassManager(Player player) {
|
||||||
super(player);
|
super(player);
|
||||||
|
|
||||||
this.ownerUid = player.getUid();
|
this.ownerUid = player.getUid();
|
||||||
this.missions = new HashMap<>();
|
|
||||||
this.takenRewards = new HashMap<>();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setPlayer(Player player) {
|
public void setPlayer(Player player) {
|
||||||
|
@ -18,8 +18,8 @@ public class EntityHomeAnimal extends EntityMonster implements Rebornable {
|
|||||||
@Getter private final int rebirthCD;
|
@Getter private final int rebirthCD;
|
||||||
private final AtomicBoolean disappeared = new AtomicBoolean();
|
private final AtomicBoolean disappeared = new AtomicBoolean();
|
||||||
|
|
||||||
public EntityHomeAnimal(Scene scene, HomeWorldAnimalData data, Position pos) {
|
public EntityHomeAnimal(Scene scene, HomeWorldAnimalData data, Position pos, Position rot) {
|
||||||
super(scene, GameData.getMonsterDataMap().get(data.getMonsterID()), pos, 1);
|
super(scene, GameData.getMonsterDataMap().get(data.getMonsterID()), pos, rot, 1);
|
||||||
|
|
||||||
this.rebornPos = pos.clone();
|
this.rebornPos = pos.clone();
|
||||||
this.rebirth = data.getIsRebirth();
|
this.rebirth = data.getIsRebirth();
|
||||||
|
@ -54,14 +54,14 @@ public class EntityMonster extends GameEntity {
|
|||||||
@Getter private List<Player> playerOnBattle;
|
@Getter private List<Player> playerOnBattle;
|
||||||
@Nullable @Getter @Setter private SceneMonster metaMonster;
|
@Nullable @Getter @Setter private SceneMonster metaMonster;
|
||||||
|
|
||||||
public EntityMonster(Scene scene, MonsterData monsterData, Position pos, int level) {
|
public EntityMonster(Scene scene, MonsterData monsterData, Position pos, Position rot, int level) {
|
||||||
super(scene);
|
super(scene);
|
||||||
|
|
||||||
this.id = this.getWorld().getNextEntityId(EntityIdType.MONSTER);
|
this.id = this.getWorld().getNextEntityId(EntityIdType.MONSTER);
|
||||||
this.monsterData = monsterData;
|
this.monsterData = monsterData;
|
||||||
this.fightProperties = new Int2FloatOpenHashMap();
|
this.fightProperties = new Int2FloatOpenHashMap();
|
||||||
this.position = new Position(pos);
|
this.position = new Position(pos);
|
||||||
this.rotation = new Position();
|
this.rotation = new Position(rot);
|
||||||
this.bornPos = this.getPosition().clone();
|
this.bornPos = this.getPosition().clone();
|
||||||
this.level = level;
|
this.level = level;
|
||||||
this.playerOnBattle = new ArrayList<>();
|
this.playerOnBattle = new ArrayList<>();
|
||||||
|
@ -15,9 +15,8 @@ import emu.grasscutter.scripts.data.controller.EntityController;
|
|||||||
import emu.grasscutter.server.event.entity.*;
|
import emu.grasscutter.server.event.entity.*;
|
||||||
import emu.grasscutter.server.packet.send.PacketEntityFightPropUpdateNotify;
|
import emu.grasscutter.server.packet.send.PacketEntityFightPropUpdateNotify;
|
||||||
import it.unimi.dsi.fastutil.ints.*;
|
import it.unimi.dsi.fastutil.ints.*;
|
||||||
import lombok.*;
|
|
||||||
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
import lombok.*;
|
||||||
|
|
||||||
public abstract class GameEntity {
|
public abstract class GameEntity {
|
||||||
@Getter private final Scene scene;
|
@Getter private final Scene scene;
|
||||||
@ -35,7 +34,8 @@ public abstract class GameEntity {
|
|||||||
@Getter @Setter private boolean lockHP;
|
@Getter @Setter private boolean lockHP;
|
||||||
|
|
||||||
@Setter(AccessLevel.PROTECTED)
|
@Setter(AccessLevel.PROTECTED)
|
||||||
@Getter private boolean isDead = false;
|
@Getter
|
||||||
|
private boolean isDead = false;
|
||||||
|
|
||||||
// Lua controller for specific actions
|
// Lua controller for specific actions
|
||||||
@Getter @Setter private EntityController entityController;
|
@Getter @Setter private EntityController entityController;
|
||||||
|
@ -107,7 +107,8 @@ public class HomeSceneItem {
|
|||||||
return new EntityHomeAnimal(
|
return new EntityHomeAnimal(
|
||||||
scene,
|
scene,
|
||||||
GameData.getHomeWorldAnimalDataMap().get(homeAnimalItem.getFurnitureId()),
|
GameData.getHomeWorldAnimalDataMap().get(homeAnimalItem.getFurnitureId()),
|
||||||
homeAnimalItem.getSpawnPos());
|
homeAnimalItem.getSpawnPos(),
|
||||||
|
homeAnimalItem.getSpawnRot());
|
||||||
})
|
})
|
||||||
.toList();
|
.toList();
|
||||||
}
|
}
|
||||||
|
@ -25,7 +25,6 @@ public class HomeWorld extends World {
|
|||||||
|
|
||||||
this.home = owner.isOnline() ? owner.getHome() : GameHome.getByUid(owner.getUid());
|
this.home = owner.isOnline() ? owner.getHome() : GameHome.getByUid(owner.getUid());
|
||||||
this.refreshModuleManager();
|
this.refreshModuleManager();
|
||||||
server.registerHomeWorld(this);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -20,14 +20,13 @@ import emu.grasscutter.net.proto.ReliquaryOuterClass.Reliquary;
|
|||||||
import emu.grasscutter.net.proto.SceneReliquaryInfoOuterClass.SceneReliquaryInfo;
|
import emu.grasscutter.net.proto.SceneReliquaryInfoOuterClass.SceneReliquaryInfo;
|
||||||
import emu.grasscutter.net.proto.SceneWeaponInfoOuterClass.SceneWeaponInfo;
|
import emu.grasscutter.net.proto.SceneWeaponInfoOuterClass.SceneWeaponInfo;
|
||||||
import emu.grasscutter.net.proto.WeaponOuterClass.Weapon;
|
import emu.grasscutter.net.proto.WeaponOuterClass.Weapon;
|
||||||
import emu.grasscutter.utils.objects.*;
|
import emu.grasscutter.utils.objects.WeightedList;
|
||||||
|
import java.util.*;
|
||||||
import lombok.*;
|
import lombok.*;
|
||||||
import org.bson.types.ObjectId;
|
import org.bson.types.ObjectId;
|
||||||
|
|
||||||
import java.util.*;
|
|
||||||
|
|
||||||
@Entity(value = "items", useDiscriminator = false)
|
@Entity(value = "items", useDiscriminator = false)
|
||||||
public class GameItem implements DatabaseObject<GameItem> {
|
public class GameItem {
|
||||||
@Id private ObjectId id;
|
@Id private ObjectId id;
|
||||||
@Indexed private int ownerId;
|
@Indexed private int ownerId;
|
||||||
@Getter @Setter private int itemId;
|
@Getter @Setter private int itemId;
|
||||||
@ -262,36 +261,14 @@ public class GameItem implements DatabaseObject<GameItem> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Saves this object to the database.
|
|
||||||
* As of Grasscutter 1.7.1, this is by default a {@link DatabaseObject#deferSave()} call.
|
|
||||||
*/
|
|
||||||
public void save() {
|
public void save() {
|
||||||
if (this.count > 0 && this.ownerId > 0) {
|
if (this.count > 0 && this.ownerId > 0) {
|
||||||
this.deferSave();
|
DatabaseHelper.saveItem(this);
|
||||||
} else {
|
} else if (this.getObjectId() != null) {
|
||||||
DatabaseHelper.deleteItem(this);
|
DatabaseHelper.deleteItem(this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Saves this object to the database.
|
|
||||||
*
|
|
||||||
* @param immediate If true, this will be a {@link DatabaseObject#save()} call instead of a {@link DatabaseObject#deferSave()} call.
|
|
||||||
*/
|
|
||||||
public void save(boolean immediate) {
|
|
||||||
if (this.count < 0 || this.ownerId <= 0) {
|
|
||||||
DatabaseHelper.deleteItem(this);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (immediate) {
|
|
||||||
DatabaseObject.super.save();
|
|
||||||
} else {
|
|
||||||
this.save();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public SceneWeaponInfo createSceneWeaponInfo() {
|
public SceneWeaponInfo createSceneWeaponInfo() {
|
||||||
var weaponInfo =
|
var weaponInfo =
|
||||||
SceneWeaponInfo.newBuilder()
|
SceneWeaponInfo.newBuilder()
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
package emu.grasscutter.game.inventory;
|
package emu.grasscutter.game.inventory;
|
||||||
|
|
||||||
|
import static emu.grasscutter.config.Configuration.INVENTORY_LIMITS;
|
||||||
|
|
||||||
import emu.grasscutter.Grasscutter;
|
import emu.grasscutter.Grasscutter;
|
||||||
import emu.grasscutter.data.GameData;
|
import emu.grasscutter.data.GameData;
|
||||||
import emu.grasscutter.data.common.ItemParamData;
|
import emu.grasscutter.data.common.ItemParamData;
|
||||||
@ -16,12 +18,9 @@ import emu.grasscutter.server.packet.send.*;
|
|||||||
import emu.grasscutter.utils.Utils;
|
import emu.grasscutter.utils.Utils;
|
||||||
import it.unimi.dsi.fastutil.ints.*;
|
import it.unimi.dsi.fastutil.ints.*;
|
||||||
import it.unimi.dsi.fastutil.longs.*;
|
import it.unimi.dsi.fastutil.longs.*;
|
||||||
import lombok.val;
|
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
import javax.annotation.Nullable;
|
||||||
import static emu.grasscutter.config.Configuration.INVENTORY_LIMITS;
|
import lombok.val;
|
||||||
|
|
||||||
public class Inventory extends BasePlayerManager implements Iterable<GameItem> {
|
public class Inventory extends BasePlayerManager implements Iterable<GameItem> {
|
||||||
private final Long2ObjectMap<GameItem> store;
|
private final Long2ObjectMap<GameItem> store;
|
||||||
@ -179,7 +178,7 @@ public class Inventory extends BasePlayerManager implements Iterable<GameItem> {
|
|||||||
changedItems.add(result);
|
changedItems.add(result);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (changedItems.isEmpty()) {
|
if (changedItems.size() == 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (reason != null) {
|
if (reason != null) {
|
||||||
@ -299,7 +298,8 @@ public class Inventory extends BasePlayerManager implements Iterable<GameItem> {
|
|||||||
|
|
||||||
// Add
|
// Add
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case ITEM_WEAPON, ITEM_RELIQUARY -> {
|
case ITEM_WEAPON:
|
||||||
|
case ITEM_RELIQUARY:
|
||||||
if (tab.getSize() >= tab.getMaxCapacity()) {
|
if (tab.getSize() >= tab.getMaxCapacity()) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@ -310,23 +310,23 @@ public class Inventory extends BasePlayerManager implements Iterable<GameItem> {
|
|||||||
// Set ownership and save to db
|
// Set ownership and save to db
|
||||||
item.save();
|
item.save();
|
||||||
return item;
|
return item;
|
||||||
}
|
case ITEM_VIRTUAL:
|
||||||
case ITEM_VIRTUAL -> {
|
|
||||||
// Handle
|
// Handle
|
||||||
this.addVirtualItem(item.getItemId(), item.getCount());
|
this.addVirtualItem(item.getItemId(), item.getCount());
|
||||||
return item;
|
return item;
|
||||||
}
|
default:
|
||||||
default -> {
|
|
||||||
switch (item.getItemData().getMaterialType()) {
|
switch (item.getItemData().getMaterialType()) {
|
||||||
case MATERIAL_AVATAR, MATERIAL_FLYCLOAK, MATERIAL_COSTUME, MATERIAL_NAMECARD -> {
|
case MATERIAL_AVATAR:
|
||||||
|
case MATERIAL_FLYCLOAK:
|
||||||
|
case MATERIAL_COSTUME:
|
||||||
|
case MATERIAL_NAMECARD:
|
||||||
Grasscutter.getLogger()
|
Grasscutter.getLogger()
|
||||||
.warn(
|
.warn(
|
||||||
"Attempted to add a "
|
"Attempted to add a "
|
||||||
+ item.getItemData().getMaterialType().name()
|
+ item.getItemData().getMaterialType().name()
|
||||||
+ " to inventory, but item definition lacks isUseOnGain. This indicates a Resources error.");
|
+ " to inventory, but item definition lacks isUseOnGain. This indicates a Resources error.");
|
||||||
return null;
|
return null;
|
||||||
}
|
default:
|
||||||
default -> {
|
|
||||||
if (tab == null) {
|
if (tab == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@ -353,8 +353,6 @@ public class Inventory extends BasePlayerManager implements Iterable<GameItem> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private synchronized void putItem(GameItem item, InventoryTab tab) {
|
private synchronized void putItem(GameItem item, InventoryTab tab) {
|
||||||
this.player.getCodex().checkAddedItem(item);
|
this.player.getCodex().checkAddedItem(item);
|
||||||
|
@ -104,7 +104,8 @@ public final class BlossomActivity {
|
|||||||
|
|
||||||
var monsterData = GameData.getMonsterDataMap().get((int) entry);
|
var monsterData = GameData.getMonsterDataMap().get((int) entry);
|
||||||
var level = scene.getEntityLevel(1, worldLevelOverride);
|
var level = scene.getEntityLevel(1, worldLevelOverride);
|
||||||
var entity = new EntityMonster(scene, monsterData, pos.nearby2d(4f), level);
|
var entity =
|
||||||
|
new EntityMonster(scene, monsterData, pos.nearby2d(4f), Position.ZERO, level);
|
||||||
scene.addEntity(entity);
|
scene.addEntity(entity);
|
||||||
newMonsters.add(entity);
|
newMonsters.add(entity);
|
||||||
}
|
}
|
||||||
|
@ -8,7 +8,7 @@ import emu.grasscutter.data.excels.world.WeatherData;
|
|||||||
import emu.grasscutter.database.DatabaseHelper;
|
import emu.grasscutter.database.DatabaseHelper;
|
||||||
import emu.grasscutter.game.*;
|
import emu.grasscutter.game.*;
|
||||||
import emu.grasscutter.game.ability.AbilityManager;
|
import emu.grasscutter.game.ability.AbilityManager;
|
||||||
import emu.grasscutter.game.achievement.*;
|
import emu.grasscutter.game.achievement.Achievements;
|
||||||
import emu.grasscutter.game.activity.ActivityManager;
|
import emu.grasscutter.game.activity.ActivityManager;
|
||||||
import emu.grasscutter.game.avatar.*;
|
import emu.grasscutter.game.avatar.*;
|
||||||
import emu.grasscutter.game.battlepass.BattlePassManager;
|
import emu.grasscutter.game.battlepass.BattlePassManager;
|
||||||
@ -55,7 +55,7 @@ import emu.grasscutter.server.game.GameSession.SessionState;
|
|||||||
import emu.grasscutter.server.packet.send.*;
|
import emu.grasscutter.server.packet.send.*;
|
||||||
import emu.grasscutter.utils.*;
|
import emu.grasscutter.utils.*;
|
||||||
import emu.grasscutter.utils.helpers.DateHelper;
|
import emu.grasscutter.utils.helpers.DateHelper;
|
||||||
import emu.grasscutter.utils.objects.*;
|
import emu.grasscutter.utils.objects.FieldFetch;
|
||||||
import it.unimi.dsi.fastutil.ints.*;
|
import it.unimi.dsi.fastutil.ints.*;
|
||||||
import lombok.*;
|
import lombok.*;
|
||||||
|
|
||||||
@ -66,7 +66,7 @@ import java.util.concurrent.*;
|
|||||||
import static emu.grasscutter.config.Configuration.GAME_OPTIONS;
|
import static emu.grasscutter.config.Configuration.GAME_OPTIONS;
|
||||||
|
|
||||||
@Entity(value = "players", useDiscriminator = false)
|
@Entity(value = "players", useDiscriminator = false)
|
||||||
public class Player implements DatabaseObject<Player>, PlayerHook, FieldFetch {
|
public class Player implements PlayerHook, FieldFetch {
|
||||||
@Id private int id;
|
@Id private int id;
|
||||||
@Indexed(options = @IndexOptions(unique = true))
|
@Indexed(options = @IndexOptions(unique = true))
|
||||||
@Getter private String accountId;
|
@Getter private String accountId;
|
||||||
@ -261,7 +261,6 @@ public class Player implements DatabaseObject<Player>, PlayerHook, FieldFetch {
|
|||||||
this.clientAbilityInitFinishHandler = new InvokeHandler(PacketClientAbilityInitFinishNotify.class);
|
this.clientAbilityInitFinishHandler = new InvokeHandler(PacketClientAbilityInitFinishNotify.class);
|
||||||
|
|
||||||
this.birthday = new PlayerBirthday();
|
this.birthday = new PlayerBirthday();
|
||||||
this.achievements = Achievements.blank();
|
|
||||||
this.rewardedLevels = new HashSet<>();
|
this.rewardedLevels = new HashSet<>();
|
||||||
this.homeRewardedLevels = new HashSet<>();
|
this.homeRewardedLevels = new HashSet<>();
|
||||||
this.seenRealmList = new HashSet<>();
|
this.seenRealmList = new HashSet<>();
|
||||||
@ -276,10 +275,8 @@ public class Player implements DatabaseObject<Player>, PlayerHook, FieldFetch {
|
|||||||
this.energyManager = new EnergyManager(this);
|
this.energyManager = new EnergyManager(this);
|
||||||
this.resinManager = new ResinManager(this);
|
this.resinManager = new ResinManager(this);
|
||||||
this.forgingManager = new ForgingManager(this);
|
this.forgingManager = new ForgingManager(this);
|
||||||
this.deforestationManager = new DeforestationManager(this);
|
|
||||||
this.progressManager = new PlayerProgressManager(this);
|
this.progressManager = new PlayerProgressManager(this);
|
||||||
this.furnitureManager = new FurnitureManager(this);
|
this.furnitureManager = new FurnitureManager(this);
|
||||||
this.battlePassManager = new BattlePassManager(this);
|
|
||||||
this.cookingManager = new CookingManager(this);
|
this.cookingManager = new CookingManager(this);
|
||||||
this.cookingCompoundManager = new CookingCompoundManager(this);
|
this.cookingCompoundManager = new CookingCompoundManager(this);
|
||||||
this.satiationManager = new SatiationManager(this);
|
this.satiationManager = new SatiationManager(this);
|
||||||
@ -303,6 +300,19 @@ public class Player implements DatabaseObject<Player>, PlayerHook, FieldFetch {
|
|||||||
this.applyStartingSceneTags();
|
this.applyStartingSceneTags();
|
||||||
this.getFlyCloakList().add(140001);
|
this.getFlyCloakList().add(140001);
|
||||||
this.getNameCardList().add(210001);
|
this.getNameCardList().add(210001);
|
||||||
|
|
||||||
|
this.mapMarksManager = new MapMarksManager(this);
|
||||||
|
this.staminaManager = new StaminaManager(this);
|
||||||
|
this.sotsManager = new SotSManager(this);
|
||||||
|
this.energyManager = new EnergyManager(this);
|
||||||
|
this.resinManager = new ResinManager(this);
|
||||||
|
this.deforestationManager = new DeforestationManager(this);
|
||||||
|
this.forgingManager = new ForgingManager(this);
|
||||||
|
this.progressManager = new PlayerProgressManager(this);
|
||||||
|
this.furnitureManager = new FurnitureManager(this);
|
||||||
|
this.cookingManager = new CookingManager(this);
|
||||||
|
this.cookingCompoundManager = new CookingCompoundManager(this);
|
||||||
|
this.satiationManager = new SatiationManager(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -1330,25 +1340,8 @@ public class Player implements DatabaseObject<Player>, PlayerHook, FieldFetch {
|
|||||||
this.getTeamManager().setPlayer(this);
|
this.getTeamManager().setPlayer(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Saves this object to the database.
|
|
||||||
* As of Grasscutter 1.7.1, this is by default a {@link DatabaseObject#deferSave()} call.
|
|
||||||
*/
|
|
||||||
public void save() {
|
public void save() {
|
||||||
this.deferSave();
|
DatabaseHelper.savePlayer(this);
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Saves this object to the database.
|
|
||||||
*
|
|
||||||
* @param immediate If true, this will be a {@link DatabaseObject#save()} call instead of a {@link DatabaseObject#deferSave()} call.
|
|
||||||
*/
|
|
||||||
public void save(boolean immediate) {
|
|
||||||
if (immediate) {
|
|
||||||
DatabaseObject.super.save();
|
|
||||||
} else {
|
|
||||||
this.save();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Called from tokenrsp
|
// Called from tokenrsp
|
||||||
@ -1385,14 +1378,6 @@ public class Player implements DatabaseObject<Player>, PlayerHook, FieldFetch {
|
|||||||
this.getPlayerProgress().setPlayer(this); // Add reference to the player.
|
this.getPlayerProgress().setPlayer(this); // Add reference to the player.
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Invoked when the player selects their avatar.
|
|
||||||
*/
|
|
||||||
public void onPlayerBorn() {
|
|
||||||
Grasscutter.getThreadPool().submit(
|
|
||||||
this.getQuestManager()::onPlayerBorn);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void onLogin() {
|
public void onLogin() {
|
||||||
// Quest - Commented out because a problem is caused if you log out while this quest is active
|
// Quest - Commented out because a problem is caused if you log out while this quest is active
|
||||||
/*
|
/*
|
||||||
@ -1519,19 +1504,20 @@ public class Player implements DatabaseObject<Player>, PlayerHook, FieldFetch {
|
|||||||
this.getProfile().syncWithCharacter(this);
|
this.getProfile().syncWithCharacter(this);
|
||||||
|
|
||||||
this.getCoopRequests().clear();
|
this.getCoopRequests().clear();
|
||||||
this.getEnterHomeRequests().values()
|
this.getEnterHomeRequests().values().forEach(req -> this.expireEnterHomeRequest(req, true));
|
||||||
.forEach(req -> this.expireEnterHomeRequest(req, true));
|
|
||||||
this.getEnterHomeRequests().clear();
|
this.getEnterHomeRequests().clear();
|
||||||
|
|
||||||
// Save to db
|
// Save to db
|
||||||
this.save(true);
|
this.save();
|
||||||
this.getTeamManager().saveAvatars();
|
this.getTeamManager().saveAvatars();
|
||||||
this.getFriendsList().save();
|
this.getFriendsList().save();
|
||||||
|
|
||||||
// Call quit event.
|
// Call quit event.
|
||||||
new PlayerQuitEvent(this).call();
|
PlayerQuitEvent event = new PlayerQuitEvent(this);
|
||||||
|
event.call();
|
||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
Grasscutter.getLogger().warn("Player (UID {}) failed to save.", this.getUid(), e);
|
e.printStackTrace();
|
||||||
|
Grasscutter.getLogger().warn("Player (UID {}) save failure", getUid());
|
||||||
} finally {
|
} finally {
|
||||||
removeFromServer();
|
removeFromServer();
|
||||||
}
|
}
|
||||||
@ -1539,10 +1525,9 @@ public class Player implements DatabaseObject<Player>, PlayerHook, FieldFetch {
|
|||||||
|
|
||||||
public void removeFromServer() {
|
public void removeFromServer() {
|
||||||
// Remove from server.
|
// Remove from server.
|
||||||
// Note: DON'T DELETE BY UID, BECAUSE THERE ARE MULTIPLE SAME UID PLAYERS WHEN DUPLICATED LOGIN!
|
//Note: DON'T DELETE BY UID,BECAUSE THERE ARE MULTIPLE SAME UID PLAYERS WHEN DUPLICATED LOGIN!
|
||||||
//s o I decide to delete by object rather than uid
|
//so I decide to delete by object rather than uid
|
||||||
this.getServer().getPlayers().values()
|
getServer().getPlayers().values().removeIf(player1 -> player1 == this);
|
||||||
.removeIf(player1 -> player1 == this);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getLegendaryKey() {
|
public int getLegendaryKey() {
|
||||||
|
@ -33,12 +33,9 @@ public final class PlayerProgressManager extends BasePlayerDataManager {
|
|||||||
|
|
||||||
public static final Set<Integer> IGNORED_OPEN_STATES =
|
public static final Set<Integer> IGNORED_OPEN_STATES =
|
||||||
Set.of(
|
Set.of(
|
||||||
1404, // OPEN_STATE_MENGDE_INFUSEDCRYSTAL, causes quest 'Mine Craft' to be given to the
|
1404 // OPEN_STATE_MENGDE_INFUSEDCRYSTAL, causes quest 'Mine Craft' to be given to the
|
||||||
// player at the start of the game.
|
// player at the start of the game.
|
||||||
// This should be removed when city reputation is implemented.
|
// This should be removed when city reputation is implemented.
|
||||||
57 // OPEN_STATE_PERSONAL_LINE, causes the prompt for showing character hangout quests to
|
|
||||||
// be permanently shown.
|
|
||||||
// This should be removed when character story quests are implemented.
|
|
||||||
);
|
);
|
||||||
// Set of open states that are set per default for all accounts. Can be overwritten by an entry in
|
// Set of open states that are set per default for all accounts. Can be overwritten by an entry in
|
||||||
// `map`.
|
// `map`.
|
||||||
|
@ -1,10 +1,11 @@
|
|||||||
package emu.grasscutter.game.player;
|
package emu.grasscutter.game.player;
|
||||||
|
|
||||||
|
import static emu.grasscutter.config.Configuration.GAME_OPTIONS;
|
||||||
|
|
||||||
import dev.morphia.annotations.*;
|
import dev.morphia.annotations.*;
|
||||||
import emu.grasscutter.*;
|
import emu.grasscutter.*;
|
||||||
import emu.grasscutter.data.GameData;
|
import emu.grasscutter.data.GameData;
|
||||||
import emu.grasscutter.data.excels.avatar.AvatarSkillDepotData;
|
import emu.grasscutter.data.excels.avatar.AvatarSkillDepotData;
|
||||||
import emu.grasscutter.database.Database;
|
|
||||||
import emu.grasscutter.game.avatar.Avatar;
|
import emu.grasscutter.game.avatar.Avatar;
|
||||||
import emu.grasscutter.game.entity.*;
|
import emu.grasscutter.game.entity.*;
|
||||||
import emu.grasscutter.game.props.*;
|
import emu.grasscutter.game.props.*;
|
||||||
@ -22,12 +23,9 @@ import emu.grasscutter.server.packet.send.*;
|
|||||||
import emu.grasscutter.utils.Utils;
|
import emu.grasscutter.utils.Utils;
|
||||||
import it.unimi.dsi.fastutil.ints.*;
|
import it.unimi.dsi.fastutil.ints.*;
|
||||||
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
|
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
|
||||||
import lombok.*;
|
|
||||||
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
import lombok.*;
|
||||||
import static emu.grasscutter.config.Configuration.GAME_OPTIONS;
|
|
||||||
|
|
||||||
@Entity
|
@Entity
|
||||||
public final class TeamManager extends BasePlayerDataManager {
|
public final class TeamManager extends BasePlayerDataManager {
|
||||||
@ -406,7 +404,7 @@ public final class TeamManager extends BasePlayerDataManager {
|
|||||||
// Unload removed entities
|
// Unload removed entities
|
||||||
for (var entity : existingAvatars.values()) {
|
for (var entity : existingAvatars.values()) {
|
||||||
this.getPlayer().getScene().removeEntity(entity);
|
this.getPlayer().getScene().removeEntity(entity);
|
||||||
entity.getAvatar().save(true);
|
entity.getAvatar().save();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set new selected character index
|
// Set new selected character index
|
||||||
@ -964,13 +962,11 @@ public final class TeamManager extends BasePlayerDataManager {
|
|||||||
return respawnPoint.get().getPointData().getTranPos();
|
return respawnPoint.get().getPointData().getTranPos();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Performs a bulk save operation on all avatars.
|
|
||||||
*/
|
|
||||||
public void saveAvatars() {
|
public void saveAvatars() {
|
||||||
Database.saveAll(this.getActiveTeam().stream()
|
// Save all avatars from active team
|
||||||
.map(EntityAvatar::getAvatar)
|
for (EntityAvatar entity : this.getActiveTeam()) {
|
||||||
.toList());
|
entity.getAvatar().save();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void onPlayerLogin() { // Hack for now to fix resonances on login
|
public void onPlayerLogin() { // Hack for now to fix resonances on login
|
||||||
|
@ -1,5 +1,8 @@
|
|||||||
package emu.grasscutter.game.quest;
|
package emu.grasscutter.game.quest;
|
||||||
|
|
||||||
|
import static emu.grasscutter.GameConstants.DEBUG;
|
||||||
|
import static emu.grasscutter.config.Configuration.*;
|
||||||
|
|
||||||
import emu.grasscutter.Grasscutter;
|
import emu.grasscutter.Grasscutter;
|
||||||
import emu.grasscutter.data.GameData;
|
import emu.grasscutter.data.GameData;
|
||||||
import emu.grasscutter.data.binout.*;
|
import emu.grasscutter.data.binout.*;
|
||||||
@ -12,16 +15,12 @@ import emu.grasscutter.net.proto.GivingRecordOuterClass.GivingRecord;
|
|||||||
import emu.grasscutter.server.packet.send.*;
|
import emu.grasscutter.server.packet.send.*;
|
||||||
import io.netty.util.concurrent.FastThreadLocalThread;
|
import io.netty.util.concurrent.FastThreadLocalThread;
|
||||||
import it.unimi.dsi.fastutil.ints.*;
|
import it.unimi.dsi.fastutil.ints.*;
|
||||||
import lombok.*;
|
|
||||||
|
|
||||||
import javax.annotation.Nonnull;
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.concurrent.*;
|
import java.util.concurrent.*;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
import static emu.grasscutter.GameConstants.DEBUG;
|
import lombok.*;
|
||||||
import static emu.grasscutter.config.Configuration.*;
|
|
||||||
|
|
||||||
public final class QuestManager extends BasePlayerManager {
|
public final class QuestManager extends BasePlayerManager {
|
||||||
@Getter private final Player player;
|
@Getter private final Player player;
|
||||||
@ -222,14 +221,11 @@ public final class QuestManager extends BasePlayerManager {
|
|||||||
this.player.sendPacket(new PacketGivingRecordNotify(this.getGivingRecords()));
|
this.player.sendPacket(new PacketGivingRecordNotify(this.getGivingRecords()));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void onPlayerBorn() {
|
public void onLogin() {
|
||||||
if (this.isQuestingEnabled()) {
|
if (this.isQuestingEnabled()) {
|
||||||
this.enableQuests();
|
this.enableQuests();
|
||||||
this.sendGivingRecords();
|
this.sendGivingRecords();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
public void onLogin() {
|
|
||||||
|
|
||||||
List<GameMainQuest> activeQuests = getActiveMainQuests();
|
List<GameMainQuest> activeQuests = getActiveMainQuests();
|
||||||
List<GameQuest> activeSubs = new ArrayList<>(activeQuests.size());
|
List<GameQuest> activeSubs = new ArrayList<>(activeQuests.size());
|
||||||
@ -570,7 +566,7 @@ public final class QuestManager extends BasePlayerManager {
|
|||||||
* @param quest The ID of the quest.
|
* @param quest The ID of the quest.
|
||||||
*/
|
*/
|
||||||
public void checkQuestAlreadyFulfilled(GameQuest quest) {
|
public void checkQuestAlreadyFulfilled(GameQuest quest) {
|
||||||
eventExecutor
|
Grasscutter.getThreadPool()
|
||||||
.submit(
|
.submit(
|
||||||
() -> {
|
() -> {
|
||||||
for (var condition : quest.getQuestData().getFinishCond()) {
|
for (var condition : quest.getQuestData().getFinishCond()) {
|
||||||
|
@ -0,0 +1,21 @@
|
|||||||
|
package emu.grasscutter.game.quest.conditions;
|
||||||
|
|
||||||
|
import emu.grasscutter.data.excels.quest.QuestData;
|
||||||
|
import emu.grasscutter.game.player.Player;
|
||||||
|
import emu.grasscutter.game.quest.QuestValueCond;
|
||||||
|
import emu.grasscutter.game.quest.enums.QuestCond;
|
||||||
|
|
||||||
|
@QuestValueCond(QuestCond.QUEST_COND_MAIN_COOP_START)
|
||||||
|
public class ConditionMainCoopStart extends BaseCondition {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean execute(
|
||||||
|
Player owner,
|
||||||
|
QuestData questData,
|
||||||
|
QuestData.QuestAcceptCondition condition,
|
||||||
|
String paramStr,
|
||||||
|
int... params) {
|
||||||
|
return condition.getParam()[0] == params[0]
|
||||||
|
&& (condition.getParam()[1] == 0 || condition.getParam()[1] == params[1]);
|
||||||
|
}
|
||||||
|
}
|
@ -54,7 +54,7 @@ public enum QuestCond implements QuestTrigger {
|
|||||||
QUEST_COND_QUEST_GLOBAL_VAR_LESS(46),
|
QUEST_COND_QUEST_GLOBAL_VAR_LESS(46),
|
||||||
QUEST_COND_PERSONAL_LINE_UNLOCK(47),
|
QUEST_COND_PERSONAL_LINE_UNLOCK(47),
|
||||||
QUEST_COND_CITY_REPUTATION_REQUEST(48), // missing
|
QUEST_COND_CITY_REPUTATION_REQUEST(48), // missing
|
||||||
QUEST_COND_MAIN_COOP_START(49), // missing
|
QUEST_COND_MAIN_COOP_START(49),
|
||||||
QUEST_COND_MAIN_COOP_ENTER_SAVE_POINT(50), // missing
|
QUEST_COND_MAIN_COOP_ENTER_SAVE_POINT(50), // missing
|
||||||
QUEST_COND_CITY_REPUTATION_LEVEL(51), // missing, only NPC groups
|
QUEST_COND_CITY_REPUTATION_LEVEL(51), // missing, only NPC groups
|
||||||
QUEST_COND_CITY_REPUTATION_UNLOCK(52), // missing, currently unused
|
QUEST_COND_CITY_REPUTATION_UNLOCK(52), // missing, currently unused
|
||||||
|
@ -816,8 +816,8 @@ public class Scene {
|
|||||||
|
|
||||||
int level = this.getEntityLevel(entry.getLevel(), worldLevelOverride);
|
int level = this.getEntityLevel(entry.getLevel(), worldLevelOverride);
|
||||||
|
|
||||||
EntityMonster monster = new EntityMonster(this, data, entry.getPos(), level);
|
EntityMonster monster =
|
||||||
monster.getRotation().set(entry.getRot());
|
new EntityMonster(this, data, entry.getPos(), entry.getRot(), level);
|
||||||
monster.setGroupId(entry.getGroup().getGroupId());
|
monster.setGroupId(entry.getGroup().getGroupId());
|
||||||
monster.setPoseId(entry.getPoseId());
|
monster.setPoseId(entry.getPoseId());
|
||||||
monster.setConfigId(entry.getConfigId());
|
monster.setConfigId(entry.getConfigId());
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
package emu.grasscutter.game.world;
|
package emu.grasscutter.game.world;
|
||||||
|
|
||||||
|
import static emu.grasscutter.server.event.player.PlayerTeleportEvent.TeleportType.SCRIPT;
|
||||||
|
|
||||||
import emu.grasscutter.data.GameData;
|
import emu.grasscutter.data.GameData;
|
||||||
import emu.grasscutter.data.excels.dungeon.DungeonData;
|
import emu.grasscutter.data.excels.dungeon.DungeonData;
|
||||||
import emu.grasscutter.game.entity.*;
|
import emu.grasscutter.game.entity.*;
|
||||||
@ -18,13 +20,10 @@ import emu.grasscutter.server.game.GameServer;
|
|||||||
import emu.grasscutter.server.packet.send.*;
|
import emu.grasscutter.server.packet.send.*;
|
||||||
import emu.grasscutter.utils.ConversionUtils;
|
import emu.grasscutter.utils.ConversionUtils;
|
||||||
import it.unimi.dsi.fastutil.ints.*;
|
import it.unimi.dsi.fastutil.ints.*;
|
||||||
|
import java.util.*;
|
||||||
import lombok.*;
|
import lombok.*;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
import java.util.*;
|
|
||||||
|
|
||||||
import static emu.grasscutter.server.event.player.PlayerTeleportEvent.TeleportType.SCRIPT;
|
|
||||||
|
|
||||||
public class World implements Iterable<Player> {
|
public class World implements Iterable<Player> {
|
||||||
@Getter private final GameServer server;
|
@Getter private final GameServer server;
|
||||||
@Getter private Player host;
|
@Getter private Player host;
|
||||||
@ -73,6 +72,8 @@ public class World implements Iterable<Player> {
|
|||||||
this.scenes = Int2ObjectMaps.synchronize(new Int2ObjectOpenHashMap<>());
|
this.scenes = Int2ObjectMaps.synchronize(new Int2ObjectOpenHashMap<>());
|
||||||
this.entity = new EntityWorld(this);
|
this.entity = new EntityWorld(this);
|
||||||
this.lastUpdateTime = System.currentTimeMillis();
|
this.lastUpdateTime = System.currentTimeMillis();
|
||||||
|
|
||||||
|
server.registerWorld(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getLevelEntityId() {
|
public int getLevelEntityId() {
|
||||||
@ -267,7 +268,7 @@ public class World implements Iterable<Player> {
|
|||||||
scene.removePlayer(player);
|
scene.removePlayer(player);
|
||||||
|
|
||||||
// Info packet for other players
|
// Info packet for other players
|
||||||
if (!this.getPlayers().isEmpty()) {
|
if (this.getPlayers().size() > 0) {
|
||||||
this.updatePlayerInfos(player);
|
this.updatePlayerInfos(player);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -397,19 +398,31 @@ public class World implements Iterable<Player> {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
Scene oldScene = null;
|
Scene oldScene = player.getScene();
|
||||||
if (player.getScene() != null) {
|
var newScene = this.getSceneById(teleportProperties.getSceneId());
|
||||||
oldScene = player.getScene();
|
|
||||||
|
|
||||||
// Don't deregister scenes if the player is going to tp back into them
|
// Move directly in the same scene.
|
||||||
if (oldScene.getId() == teleportProperties.getSceneId()) {
|
if (newScene == oldScene && teleportProperties.getTeleportType() == TeleportType.COMMAND) {
|
||||||
oldScene.setDontDestroyWhenEmpty(true);
|
// Set player position and rotation
|
||||||
|
if (teleportProperties.getTeleportTo() != null) {
|
||||||
|
player.getPosition().set(teleportProperties.getTeleportTo());
|
||||||
|
}
|
||||||
|
if (teleportProperties.getTeleportRot() != null) {
|
||||||
|
player.getRotation().set(teleportProperties.getTeleportRot());
|
||||||
|
}
|
||||||
|
player.sendPacket(new PacketSceneEntityAppearNotify(player));
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (oldScene != null) {
|
||||||
|
// Don't deregister scenes if the player is going to tp back into them
|
||||||
|
if (oldScene == newScene) {
|
||||||
|
oldScene.setDontDestroyWhenEmpty(true);
|
||||||
|
}
|
||||||
oldScene.removePlayer(player);
|
oldScene.removePlayer(player);
|
||||||
}
|
}
|
||||||
|
|
||||||
var newScene = this.getSceneById(teleportProperties.getSceneId());
|
if (newScene != null) {
|
||||||
newScene.addPlayer(player);
|
newScene.addPlayer(player);
|
||||||
|
|
||||||
player.getTeamManager().applyAbilities(newScene);
|
player.getTeamManager().applyAbilities(newScene);
|
||||||
@ -430,6 +443,7 @@ public class World implements Iterable<Player> {
|
|||||||
teleportProperties.setTeleportRot(config.born_rot);
|
teleportProperties.setTeleportRot(config.born_rot);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Set player position and rotation
|
// Set player position and rotation
|
||||||
if (teleportProperties.getTeleportTo() != null) {
|
if (teleportProperties.getTeleportTo() != null) {
|
||||||
@ -439,7 +453,7 @@ public class World implements Iterable<Player> {
|
|||||||
player.getRotation().set(teleportProperties.getTeleportRot());
|
player.getRotation().set(teleportProperties.getTeleportRot());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (oldScene != null && newScene != oldScene) {
|
if (oldScene != null && newScene != null && newScene != oldScene) {
|
||||||
newScene.setPrevScenePoint(oldScene.getPrevScenePoint());
|
newScene.setPrevScenePoint(oldScene.getPrevScenePoint());
|
||||||
oldScene.setDontDestroyWhenEmpty(false);
|
oldScene.setDontDestroyWhenEmpty(false);
|
||||||
}
|
}
|
||||||
|
@ -1,22 +0,0 @@
|
|||||||
package emu.grasscutter.net;
|
|
||||||
|
|
||||||
public interface KcpChannel {
|
|
||||||
/**
|
|
||||||
* Event fired when the client connects.
|
|
||||||
*
|
|
||||||
* @param tunnel The tunnel.
|
|
||||||
*/
|
|
||||||
void onConnected(KcpTunnel tunnel);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Event fired when the client disconnects.
|
|
||||||
*/
|
|
||||||
void onDisconnected();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Event fired when data is received from the client.
|
|
||||||
*
|
|
||||||
* @param bytes The data received.
|
|
||||||
*/
|
|
||||||
void onMessage(byte[] bytes);
|
|
||||||
}
|
|
@ -1,22 +0,0 @@
|
|||||||
package emu.grasscutter.net;
|
|
||||||
|
|
||||||
import java.net.InetSocketAddress;
|
|
||||||
|
|
||||||
public interface KcpTunnel {
|
|
||||||
/**
|
|
||||||
* @return The address of the client.
|
|
||||||
*/
|
|
||||||
InetSocketAddress getAddress();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sends bytes to the client.
|
|
||||||
*
|
|
||||||
* @param bytes The bytes to send.
|
|
||||||
*/
|
|
||||||
void writeData(byte[] bytes);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Closes the connection.
|
|
||||||
*/
|
|
||||||
void close();
|
|
||||||
}
|
|
@ -1037,8 +1037,7 @@ public class SceneScriptManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Spawn mob
|
// Spawn mob
|
||||||
EntityMonster entity = new EntityMonster(getScene(), data, monster.pos, level);
|
EntityMonster entity = new EntityMonster(getScene(), data, monster.pos, monster.rot, level);
|
||||||
entity.getRotation().set(monster.rot);
|
|
||||||
entity.setGroupId(groupId);
|
entity.setGroupId(groupId);
|
||||||
entity.setBlockId(blockId);
|
entity.setBlockId(blockId);
|
||||||
entity.setConfigId(monster.config_id);
|
entity.setConfigId(monster.config_id);
|
||||||
|
@ -1,49 +1,55 @@
|
|||||||
package emu.grasscutter.server.game;
|
package emu.grasscutter.server.game;
|
||||||
|
|
||||||
|
import static emu.grasscutter.config.Configuration.*;
|
||||||
|
import static emu.grasscutter.utils.lang.Language.translate;
|
||||||
|
|
||||||
import emu.grasscutter.*;
|
import emu.grasscutter.*;
|
||||||
import emu.grasscutter.Grasscutter.ServerRunMode;
|
import emu.grasscutter.Grasscutter.ServerRunMode;
|
||||||
import emu.grasscutter.database.DatabaseHelper;
|
import emu.grasscutter.database.DatabaseHelper;
|
||||||
import emu.grasscutter.game.Account;
|
import emu.grasscutter.game.Account;
|
||||||
import emu.grasscutter.game.battlepass.BattlePassSystem;
|
import emu.grasscutter.game.battlepass.BattlePassSystem;
|
||||||
import emu.grasscutter.game.chat.*;
|
import emu.grasscutter.game.chat.ChatSystem;
|
||||||
|
import emu.grasscutter.game.chat.ChatSystemHandler;
|
||||||
import emu.grasscutter.game.combine.CombineManger;
|
import emu.grasscutter.game.combine.CombineManger;
|
||||||
import emu.grasscutter.game.drop.*;
|
import emu.grasscutter.game.drop.DropSystem;
|
||||||
|
import emu.grasscutter.game.drop.DropSystemLegacy;
|
||||||
import emu.grasscutter.game.dungeons.DungeonSystem;
|
import emu.grasscutter.game.dungeons.DungeonSystem;
|
||||||
import emu.grasscutter.game.expedition.ExpeditionSystem;
|
import emu.grasscutter.game.expedition.ExpeditionSystem;
|
||||||
import emu.grasscutter.game.gacha.GachaSystem;
|
import emu.grasscutter.game.gacha.GachaSystem;
|
||||||
import emu.grasscutter.game.home.*;
|
import emu.grasscutter.game.home.HomeWorld;
|
||||||
import emu.grasscutter.game.managers.cooking.*;
|
import emu.grasscutter.game.home.HomeWorldMPSystem;
|
||||||
|
import emu.grasscutter.game.managers.cooking.CookingCompoundManager;
|
||||||
|
import emu.grasscutter.game.managers.cooking.CookingManager;
|
||||||
import emu.grasscutter.game.managers.energy.EnergyManager;
|
import emu.grasscutter.game.managers.energy.EnergyManager;
|
||||||
import emu.grasscutter.game.managers.stamina.StaminaManager;
|
import emu.grasscutter.game.managers.stamina.StaminaManager;
|
||||||
import emu.grasscutter.game.player.Player;
|
import emu.grasscutter.game.player.Player;
|
||||||
import emu.grasscutter.game.quest.QuestSystem;
|
import emu.grasscutter.game.quest.QuestSystem;
|
||||||
import emu.grasscutter.game.shop.ShopSystem;
|
import emu.grasscutter.game.shop.ShopSystem;
|
||||||
import emu.grasscutter.game.systems.*;
|
import emu.grasscutter.game.systems.AnnouncementSystem;
|
||||||
|
import emu.grasscutter.game.systems.InventorySystem;
|
||||||
|
import emu.grasscutter.game.systems.MultiplayerSystem;
|
||||||
import emu.grasscutter.game.talk.TalkSystem;
|
import emu.grasscutter.game.talk.TalkSystem;
|
||||||
import emu.grasscutter.game.tower.TowerSystem;
|
import emu.grasscutter.game.tower.TowerSystem;
|
||||||
import emu.grasscutter.game.world.*;
|
import emu.grasscutter.game.world.World;
|
||||||
|
import emu.grasscutter.game.world.WorldDataSystem;
|
||||||
import emu.grasscutter.net.packet.PacketHandler;
|
import emu.grasscutter.net.packet.PacketHandler;
|
||||||
import emu.grasscutter.net.proto.SocialDetailOuterClass.SocialDetail;
|
import emu.grasscutter.net.proto.SocialDetailOuterClass.SocialDetail;
|
||||||
import emu.grasscutter.server.dispatch.DispatchClient;
|
import emu.grasscutter.server.dispatch.DispatchClient;
|
||||||
import emu.grasscutter.server.event.game.ServerTickEvent;
|
import emu.grasscutter.server.event.game.ServerTickEvent;
|
||||||
import emu.grasscutter.server.event.internal.*;
|
import emu.grasscutter.server.event.internal.ServerStartEvent;
|
||||||
|
import emu.grasscutter.server.event.internal.ServerStopEvent;
|
||||||
import emu.grasscutter.server.event.types.ServerEvent;
|
import emu.grasscutter.server.event.types.ServerEvent;
|
||||||
import emu.grasscutter.server.scheduler.ServerTaskScheduler;
|
import emu.grasscutter.server.scheduler.ServerTaskScheduler;
|
||||||
import emu.grasscutter.task.TaskMap;
|
import emu.grasscutter.task.TaskMap;
|
||||||
import emu.grasscutter.utils.Utils;
|
import emu.grasscutter.utils.Utils;
|
||||||
import it.unimi.dsi.fastutil.ints.*;
|
import it.unimi.dsi.fastutil.ints.*;
|
||||||
import kcp.highway.*;
|
|
||||||
import lombok.*;
|
|
||||||
import org.jetbrains.annotations.*;
|
|
||||||
import emu.grasscutter.server.game.session.GameSessionManager;
|
|
||||||
|
|
||||||
import java.net.*;
|
import java.net.*;
|
||||||
import java.time.*;
|
import java.time.*;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.concurrent.*;
|
import java.util.concurrent.*;
|
||||||
|
import kcp.highway.*;
|
||||||
import static emu.grasscutter.config.Configuration.*;
|
import lombok.*;
|
||||||
import static emu.grasscutter.utils.lang.Language.translate;
|
import org.jetbrains.annotations.*;
|
||||||
|
|
||||||
@Getter
|
@Getter
|
||||||
public final class GameServer extends KcpServer implements Iterable<Player> {
|
public final class GameServer extends KcpServer implements Iterable<Player> {
|
||||||
@ -54,7 +60,6 @@ public final class GameServer extends KcpServer implements Iterable<Player> {
|
|||||||
private final Set<World> worlds;
|
private final Set<World> worlds;
|
||||||
private final Int2ObjectMap<HomeWorld> homeWorlds;
|
private final Int2ObjectMap<HomeWorld> homeWorlds;
|
||||||
|
|
||||||
@Getter private boolean started = false;
|
|
||||||
@Setter private DispatchClient dispatchClient;
|
@Setter private DispatchClient dispatchClient;
|
||||||
|
|
||||||
// Server systems
|
// Server systems
|
||||||
@ -135,7 +140,7 @@ public final class GameServer extends KcpServer implements Iterable<Player> {
|
|||||||
channelConfig.setUseConvChannel(true);
|
channelConfig.setUseConvChannel(true);
|
||||||
channelConfig.setAckNoDelay(false);
|
channelConfig.setAckNoDelay(false);
|
||||||
|
|
||||||
this.init(GameSessionManager.getInstance(), channelConfig, address);
|
this.init(GameSessionManager.getListener(), channelConfig, address);
|
||||||
|
|
||||||
EnergyManager.initialize();
|
EnergyManager.initialize();
|
||||||
StaminaManager.initialize();
|
StaminaManager.initialize();
|
||||||
@ -306,11 +311,6 @@ public final class GameServer extends KcpServer implements Iterable<Player> {
|
|||||||
world.save(); // Save the player's world
|
world.save(); // Save the player's world
|
||||||
}
|
}
|
||||||
|
|
||||||
public void registerHomeWorld(HomeWorld homeWorld) {
|
|
||||||
this.getHomeWorlds().put(homeWorld.getOwnerUid(), homeWorld);
|
|
||||||
this.registerWorld(homeWorld);
|
|
||||||
}
|
|
||||||
|
|
||||||
public HomeWorld getHomeWorldOrCreate(Player owner) {
|
public HomeWorld getHomeWorldOrCreate(Player owner) {
|
||||||
return this.getHomeWorlds()
|
return this.getHomeWorlds()
|
||||||
.computeIfAbsent(owner.getUid(), (uid) -> new HomeWorld(this, owner));
|
.computeIfAbsent(owner.getUid(), (uid) -> new HomeWorld(this, owner));
|
||||||
@ -342,8 +342,6 @@ public final class GameServer extends KcpServer implements Iterable<Player> {
|
|||||||
.info(translate("messages.game.address_bind", GAME_INFO.accessAddress, address.getPort()));
|
.info(translate("messages.game.address_bind", GAME_INFO.accessAddress, address.getPort()));
|
||||||
ServerStartEvent event = new ServerStartEvent(ServerEvent.Type.GAME, OffsetDateTime.now());
|
ServerStartEvent event = new ServerStartEvent(ServerEvent.Type.GAME, OffsetDateTime.now());
|
||||||
event.call();
|
event.call();
|
||||||
|
|
||||||
this.started = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void onServerShutdown() {
|
public void onServerShutdown() {
|
||||||
@ -358,10 +356,10 @@ public final class GameServer extends KcpServer implements Iterable<Player> {
|
|||||||
this.stop(); // Stop the server.
|
this.stop(); // Stop the server.
|
||||||
|
|
||||||
try {
|
try {
|
||||||
var threadPool = GameSessionManager.getExecutor();
|
var threadPool = GameSessionManager.getLogicThread();
|
||||||
|
|
||||||
// Shutdown network thread.
|
// Shutdown network thread.
|
||||||
threadPool.shutdown();
|
threadPool.shutdownGracefully();
|
||||||
// Wait for the network thread to finish.
|
// Wait for the network thread to finish.
|
||||||
if (!threadPool.awaitTermination(5, TimeUnit.SECONDS)) {
|
if (!threadPool.awaitTermination(5, TimeUnit.SECONDS)) {
|
||||||
Grasscutter.getLogger().error("Logic thread did not terminate!");
|
Grasscutter.getLogger().error("Logic thread did not terminate!");
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
package emu.grasscutter.server.game;
|
package emu.grasscutter.server.game;
|
||||||
|
|
||||||
|
import static emu.grasscutter.config.Configuration.GAME_INFO;
|
||||||
|
|
||||||
import emu.grasscutter.Grasscutter;
|
import emu.grasscutter.Grasscutter;
|
||||||
import emu.grasscutter.Grasscutter.ServerDebugMode;
|
import emu.grasscutter.Grasscutter.ServerDebugMode;
|
||||||
import emu.grasscutter.net.packet.*;
|
import emu.grasscutter.net.packet.*;
|
||||||
@ -7,8 +9,6 @@ import emu.grasscutter.server.event.game.ReceivePacketEvent;
|
|||||||
import emu.grasscutter.server.game.GameSession.SessionState;
|
import emu.grasscutter.server.game.GameSession.SessionState;
|
||||||
import it.unimi.dsi.fastutil.ints.*;
|
import it.unimi.dsi.fastutil.ints.*;
|
||||||
|
|
||||||
import static emu.grasscutter.config.Configuration.GAME_INFO;
|
|
||||||
|
|
||||||
public final class GameServerPacketHandler {
|
public final class GameServerPacketHandler {
|
||||||
private final Int2ObjectMap<PacketHandler> handlers;
|
private final Int2ObjectMap<PacketHandler> handlers;
|
||||||
|
|
||||||
@ -76,11 +76,13 @@ public final class GameServerPacketHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Invoke event.
|
// Invoke event.
|
||||||
var event = new ReceivePacketEvent(session, opcode, payload);
|
ReceivePacketEvent event = new ReceivePacketEvent(session, opcode, payload);
|
||||||
if (event.call()) // If event is not canceled, continue.
|
event.call();
|
||||||
|
if (!event.isCanceled()) // If event is not canceled, continue.
|
||||||
handler.handle(session, header, event.getPacketData());
|
handler.handle(session, header, event.getPacketData());
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
Grasscutter.getLogger().warn("Unable to handle packet.", ex);
|
// TODO Remove this when no more needed
|
||||||
|
ex.printStackTrace();
|
||||||
}
|
}
|
||||||
return; // Packet successfully handled
|
return; // Packet successfully handled
|
||||||
}
|
}
|
||||||
|
@ -1,26 +1,24 @@
|
|||||||
package emu.grasscutter.server.game;
|
package emu.grasscutter.server.game;
|
||||||
|
|
||||||
|
import static emu.grasscutter.config.Configuration.*;
|
||||||
|
import static emu.grasscutter.utils.lang.Language.translate;
|
||||||
|
|
||||||
import emu.grasscutter.Grasscutter;
|
import emu.grasscutter.Grasscutter;
|
||||||
import emu.grasscutter.Grasscutter.ServerDebugMode;
|
import emu.grasscutter.Grasscutter.ServerDebugMode;
|
||||||
import emu.grasscutter.game.Account;
|
import emu.grasscutter.game.Account;
|
||||||
import emu.grasscutter.game.player.Player;
|
import emu.grasscutter.game.player.Player;
|
||||||
import emu.grasscutter.net.*;
|
|
||||||
import emu.grasscutter.net.packet.*;
|
import emu.grasscutter.net.packet.*;
|
||||||
import emu.grasscutter.server.event.game.SendPacketEvent;
|
import emu.grasscutter.server.event.game.SendPacketEvent;
|
||||||
import emu.grasscutter.utils.*;
|
import emu.grasscutter.utils.*;
|
||||||
import io.netty.buffer.*;
|
import io.netty.buffer.*;
|
||||||
import lombok.*;
|
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.net.InetSocketAddress;
|
import java.net.InetSocketAddress;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
|
import lombok.*;
|
||||||
|
|
||||||
import static emu.grasscutter.config.Configuration.*;
|
public class GameSession implements GameSessionManager.KcpChannel {
|
||||||
import static emu.grasscutter.utils.lang.Language.translate;
|
|
||||||
|
|
||||||
public class GameSession implements KcpChannel {
|
|
||||||
private final GameServer server;
|
private final GameServer server;
|
||||||
private KcpTunnel tunnel;
|
private GameSessionManager.KcpTunnel tunnel;
|
||||||
|
|
||||||
@Getter @Setter private Account account;
|
@Getter @Setter private Account account;
|
||||||
@Getter private Player player;
|
@Getter private Player player;
|
||||||
@ -148,7 +146,7 @@ public class GameSession implements KcpChannel {
|
|||||||
if (packet.shouldEncrypt) {
|
if (packet.shouldEncrypt) {
|
||||||
Crypto.xor(bytes, packet.useDispatchKey() ? Crypto.DISPATCH_KEY : this.encryptKey);
|
Crypto.xor(bytes, packet.useDispatchKey() ? Crypto.DISPATCH_KEY : this.encryptKey);
|
||||||
}
|
}
|
||||||
this.tunnel.writeData(bytes);
|
tunnel.writeData(bytes);
|
||||||
} catch (Exception ignored) {
|
} catch (Exception ignored) {
|
||||||
Grasscutter.getLogger().debug("Unable to send packet to client.");
|
Grasscutter.getLogger().debug("Unable to send packet to client.");
|
||||||
}
|
}
|
||||||
@ -156,13 +154,13 @@ public class GameSession implements KcpChannel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onConnected(KcpTunnel tunnel) {
|
public void onConnected(GameSessionManager.KcpTunnel tunnel) {
|
||||||
this.tunnel = tunnel;
|
this.tunnel = tunnel;
|
||||||
Grasscutter.getLogger().info(translate("messages.game.connect", this.getAddress().toString()));
|
Grasscutter.getLogger().info(translate("messages.game.connect", this.getAddress().toString()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onMessage(byte[] bytes) {
|
public void handleReceive(byte[] bytes) {
|
||||||
// Decrypt and turn back into a packet
|
// Decrypt and turn back into a packet
|
||||||
Crypto.xor(bytes, useSecretKey() ? this.encryptKey : Crypto.DISPATCH_KEY);
|
Crypto.xor(bytes, useSecretKey() ? this.encryptKey : Crypto.DISPATCH_KEY);
|
||||||
ByteBuf packet = Unpooled.wrappedBuffer(bytes);
|
ByteBuf packet = Unpooled.wrappedBuffer(bytes);
|
||||||
@ -228,8 +226,8 @@ public class GameSession implements KcpChannel {
|
|||||||
// Handle
|
// Handle
|
||||||
getServer().getPacketHandler().handle(this, opcode, header, payload);
|
getServer().getPacketHandler().handle(this, opcode, header, payload);
|
||||||
}
|
}
|
||||||
} catch (Exception exception) {
|
} catch (Exception e) {
|
||||||
Grasscutter.getLogger().warn("Unable to handle packet.", exception);
|
e.printStackTrace();
|
||||||
} finally {
|
} finally {
|
||||||
// byteBuf.release(); //Needn't
|
// byteBuf.release(); //Needn't
|
||||||
packet.release();
|
packet.release();
|
||||||
@ -237,9 +235,8 @@ public class GameSession implements KcpChannel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onDisconnected() {
|
public void handleClose() {
|
||||||
setState(SessionState.INACTIVE);
|
setState(SessionState.INACTIVE);
|
||||||
|
|
||||||
// send disconnection pack in case of reconnection
|
// send disconnection pack in case of reconnection
|
||||||
Grasscutter.getLogger()
|
Grasscutter.getLogger()
|
||||||
.info(translate("messages.game.disconnect", this.getAddress().toString()));
|
.info(translate("messages.game.disconnect", this.getAddress().toString()));
|
||||||
|
@ -0,0 +1,114 @@
|
|||||||
|
package emu.grasscutter.server.game;
|
||||||
|
|
||||||
|
import emu.grasscutter.Grasscutter;
|
||||||
|
import emu.grasscutter.utils.Utils;
|
||||||
|
import io.netty.buffer.*;
|
||||||
|
import io.netty.channel.DefaultEventLoop;
|
||||||
|
import java.net.InetSocketAddress;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
import kcp.highway.*;
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
public class GameSessionManager {
|
||||||
|
@Getter private static final DefaultEventLoop logicThread = new DefaultEventLoop();
|
||||||
|
private static final ConcurrentHashMap<Ukcp, GameSession> sessions = new ConcurrentHashMap<>();
|
||||||
|
private static final KcpListener listener =
|
||||||
|
new KcpListener() {
|
||||||
|
@Override
|
||||||
|
public void onConnected(Ukcp ukcp) {
|
||||||
|
int times = 0;
|
||||||
|
GameServer server = Grasscutter.getGameServer();
|
||||||
|
while (server == null) { // Waiting server to establish
|
||||||
|
try {
|
||||||
|
Thread.sleep(1000);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
ukcp.close();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (times++ > 5) {
|
||||||
|
Grasscutter.getLogger().error("Service is not available!");
|
||||||
|
ukcp.close();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
server = Grasscutter.getGameServer();
|
||||||
|
}
|
||||||
|
GameSession conversation = new GameSession(server);
|
||||||
|
conversation.onConnected(
|
||||||
|
new KcpTunnel() {
|
||||||
|
@Override
|
||||||
|
public InetSocketAddress getAddress() {
|
||||||
|
return ukcp.user().getRemoteAddress();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void writeData(byte[] bytes) {
|
||||||
|
ByteBuf buf = Unpooled.wrappedBuffer(bytes);
|
||||||
|
ukcp.write(buf);
|
||||||
|
buf.release();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
ukcp.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getSrtt() {
|
||||||
|
return ukcp.srtt();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
sessions.put(ukcp, conversation);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handleReceive(ByteBuf buf, Ukcp kcp) {
|
||||||
|
var byteData = Utils.byteBufToArray(buf);
|
||||||
|
logicThread.execute(
|
||||||
|
() -> {
|
||||||
|
try {
|
||||||
|
var conversation = sessions.get(kcp);
|
||||||
|
if (conversation != null) {
|
||||||
|
conversation.handleReceive(byteData);
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handleException(Throwable ex, Ukcp ukcp) {}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handleClose(Ukcp ukcp) {
|
||||||
|
GameSession conversation = sessions.get(ukcp);
|
||||||
|
if (conversation != null) {
|
||||||
|
conversation.handleClose();
|
||||||
|
sessions.remove(ukcp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
public static KcpListener getListener() {
|
||||||
|
return listener;
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface KcpTunnel {
|
||||||
|
InetSocketAddress getAddress();
|
||||||
|
|
||||||
|
void writeData(byte[] bytes);
|
||||||
|
|
||||||
|
void close();
|
||||||
|
|
||||||
|
int getSrtt();
|
||||||
|
}
|
||||||
|
|
||||||
|
interface KcpChannel {
|
||||||
|
void onConnected(KcpTunnel tunnel);
|
||||||
|
|
||||||
|
void handleClose();
|
||||||
|
|
||||||
|
void handleReceive(byte[] bytes);
|
||||||
|
}
|
||||||
|
}
|
@ -1,31 +0,0 @@
|
|||||||
package emu.grasscutter.server.game.session;
|
|
||||||
|
|
||||||
import emu.grasscutter.net.KcpTunnel;
|
|
||||||
import io.netty.buffer.Unpooled;
|
|
||||||
import kcp.highway.Ukcp;
|
|
||||||
import lombok.*;
|
|
||||||
|
|
||||||
import java.net.InetSocketAddress;
|
|
||||||
|
|
||||||
@RequiredArgsConstructor
|
|
||||||
public final class GameSessionHandler implements KcpTunnel {
|
|
||||||
@Getter private final Ukcp handle;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public InetSocketAddress getAddress() {
|
|
||||||
return this.getHandle().user().getRemoteAddress();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void writeData(byte[] bytes) {
|
|
||||||
var buffer = Unpooled.wrappedBuffer(bytes);
|
|
||||||
this.getHandle().write(buffer);
|
|
||||||
|
|
||||||
buffer.release();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void close() {
|
|
||||||
this.getHandle().close();
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,89 +0,0 @@
|
|||||||
package emu.grasscutter.server.game.session;
|
|
||||||
|
|
||||||
import emu.grasscutter.Grasscutter;
|
|
||||||
import emu.grasscutter.server.game.*;
|
|
||||||
import emu.grasscutter.utils.Utils;
|
|
||||||
import io.netty.buffer.ByteBuf;
|
|
||||||
import kcp.highway.*;
|
|
||||||
import lombok.Getter;
|
|
||||||
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.concurrent.*;
|
|
||||||
|
|
||||||
public final class GameSessionManager implements KcpListener {
|
|
||||||
@Getter private static final GameSessionManager instance
|
|
||||||
= new GameSessionManager();
|
|
||||||
@Getter private static final ExecutorService executor
|
|
||||||
= Executors.newWorkStealingPool();
|
|
||||||
@Getter private static final Map<Ukcp, GameSession> sessions
|
|
||||||
= new ConcurrentHashMap<>();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Waits for the game server to be ready.
|
|
||||||
*
|
|
||||||
* @return The game server.
|
|
||||||
*/
|
|
||||||
private GameServer waitForServer() {
|
|
||||||
var server = Grasscutter.getGameServer();
|
|
||||||
var times = 0; while (server == null || !server.isStarted()) {
|
|
||||||
Utils.sleep(1000); // Wait 1s for the server to start.
|
|
||||||
if (times++ > 5) {
|
|
||||||
Grasscutter.getLogger().error("Game server has not started in a reasonable time.");
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
server = Grasscutter.getGameServer();
|
|
||||||
}
|
|
||||||
|
|
||||||
return server;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onConnected(Ukcp ukcp) {
|
|
||||||
// Fetch the game server.
|
|
||||||
var server = this.waitForServer();
|
|
||||||
if (server == null) {
|
|
||||||
ukcp.close();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create a new session.
|
|
||||||
var session = sessions.compute(ukcp, (k, existing) -> {
|
|
||||||
// Close an existing session.
|
|
||||||
if (existing != null) {
|
|
||||||
existing.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
return new GameSession(server);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Connect the session.
|
|
||||||
session.onConnected(new GameSessionHandler(ukcp));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void handleReceive(ByteBuf byteBuf, Ukcp ukcp) {
|
|
||||||
// Get the session.
|
|
||||||
var session = sessions.get(ukcp);
|
|
||||||
if (session == null) {
|
|
||||||
ukcp.close(); return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle the message in a separate thread.
|
|
||||||
var bytes = Utils.byteBufToArray(byteBuf);
|
|
||||||
executor.submit(() -> session.onMessage(bytes));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void handleException(Throwable throwable, Ukcp ukcp) {
|
|
||||||
Grasscutter.getLogger().error("Exception in game session.", throwable);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void handleClose(Ukcp ukcp) {
|
|
||||||
var session = sessions.remove(ukcp);
|
|
||||||
if (session != null) {
|
|
||||||
session.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,21 @@
|
|||||||
|
package emu.grasscutter.server.packet.recv;
|
||||||
|
|
||||||
|
import emu.grasscutter.Grasscutter;
|
||||||
|
import emu.grasscutter.net.packet.*;
|
||||||
|
import emu.grasscutter.net.proto.CancelCoopTaskReqOuterClass;
|
||||||
|
import emu.grasscutter.server.game.GameSession;
|
||||||
|
import emu.grasscutter.server.packet.send.PacketCancelCoopTaskRsp;
|
||||||
|
|
||||||
|
@Opcodes(PacketOpcodes.CancelCoopTaskReq)
|
||||||
|
public class HandlerCancelCoopTaskReq extends PacketHandler {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handle(GameSession session, byte[] header, byte[] payload) throws Exception {
|
||||||
|
CancelCoopTaskReqOuterClass.CancelCoopTaskReq req =
|
||||||
|
CancelCoopTaskReqOuterClass.CancelCoopTaskReq.parseFrom(payload);
|
||||||
|
var chapterId = req.getChapterId();
|
||||||
|
Grasscutter.getLogger().warn("Call to unimplemented packet CancelCoopTaskReq");
|
||||||
|
// TODO: Actually cancel the quests.
|
||||||
|
session.send(new PacketCancelCoopTaskRsp(chapterId));
|
||||||
|
}
|
||||||
|
}
|
@ -2,6 +2,7 @@ package emu.grasscutter.server.packet.recv;
|
|||||||
|
|
||||||
import emu.grasscutter.net.packet.*;
|
import emu.grasscutter.net.packet.*;
|
||||||
import emu.grasscutter.server.game.GameSession;
|
import emu.grasscutter.server.game.GameSession;
|
||||||
|
import emu.grasscutter.server.packet.send.PacketCoopDataNotify;
|
||||||
import emu.grasscutter.server.packet.send.PacketPersonalLineAllDataRsp;
|
import emu.grasscutter.server.packet.send.PacketPersonalLineAllDataRsp;
|
||||||
|
|
||||||
@Opcodes(PacketOpcodes.PersonalLineAllDataReq)
|
@Opcodes(PacketOpcodes.PersonalLineAllDataReq)
|
||||||
@ -12,5 +13,7 @@ public class HandlerPersonalLineAllDataReq extends PacketHandler {
|
|||||||
session.send(
|
session.send(
|
||||||
new PacketPersonalLineAllDataRsp(
|
new PacketPersonalLineAllDataRsp(
|
||||||
session.getPlayer().getQuestManager().getMainQuests().values()));
|
session.getPlayer().getQuestManager().getMainQuests().values()));
|
||||||
|
// TODO: this should maybe be at player login?
|
||||||
|
session.send(new PacketCoopDataNotify());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -61,7 +61,7 @@ public class HandlerQuestCreateEntityReq extends PacketHandler {
|
|||||||
val monsterId = entity.getMonsterId();
|
val monsterId = entity.getMonsterId();
|
||||||
val level = entity.getLevel();
|
val level = entity.getLevel();
|
||||||
MonsterData monsterData = GameData.getMonsterDataMap().get(monsterId);
|
MonsterData monsterData = GameData.getMonsterDataMap().get(monsterId);
|
||||||
gameEntity = new EntityMonster(scene, monsterData, pos, level);
|
gameEntity = new EntityMonster(scene, monsterData, pos, rot, level);
|
||||||
}
|
}
|
||||||
case NPC_ID -> {}
|
case NPC_ID -> {}
|
||||||
}
|
}
|
||||||
|
@ -69,7 +69,6 @@ public class HandlerSetPlayerBornDataReq extends PacketHandler {
|
|||||||
|
|
||||||
// Login done
|
// Login done
|
||||||
session.getPlayer().onLogin();
|
session.getPlayer().onLogin();
|
||||||
session.getPlayer().onPlayerBorn();
|
|
||||||
|
|
||||||
// Born resp packet
|
// Born resp packet
|
||||||
session.send(new BasePacket(PacketOpcodes.SetPlayerBornDataRsp));
|
session.send(new BasePacket(PacketOpcodes.SetPlayerBornDataRsp));
|
||||||
|
@ -0,0 +1,31 @@
|
|||||||
|
package emu.grasscutter.server.packet.recv;
|
||||||
|
|
||||||
|
import emu.grasscutter.data.GameData;
|
||||||
|
import emu.grasscutter.game.quest.enums.QuestCond;
|
||||||
|
import emu.grasscutter.net.packet.*;
|
||||||
|
import emu.grasscutter.net.proto.StartCoopPointReqOuterClass;
|
||||||
|
import emu.grasscutter.server.game.GameSession;
|
||||||
|
import emu.grasscutter.server.packet.send.PacketStartCoopPointRsp;
|
||||||
|
|
||||||
|
@Opcodes(PacketOpcodes.StartCoopPointReq)
|
||||||
|
public class HandlerStartCoopPointReq extends PacketHandler {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handle(GameSession session, byte[] header, byte[] payload) throws Exception {
|
||||||
|
StartCoopPointReqOuterClass.StartCoopPointReq req =
|
||||||
|
StartCoopPointReqOuterClass.StartCoopPointReq.parseFrom(payload);
|
||||||
|
var coopPoint = req.getCoopPoint();
|
||||||
|
|
||||||
|
var coopPointData =
|
||||||
|
GameData.getCoopPointDataMap().values().stream()
|
||||||
|
.filter(i -> i.getId() == coopPoint)
|
||||||
|
.toList();
|
||||||
|
if (!coopPointData.isEmpty()) {
|
||||||
|
var player = session.getPlayer();
|
||||||
|
var questManager = player.getQuestManager();
|
||||||
|
questManager.queueEvent(
|
||||||
|
QuestCond.QUEST_COND_MAIN_COOP_START, coopPointData.get(0).getChapterId(), 0);
|
||||||
|
}
|
||||||
|
session.send(new PacketStartCoopPointRsp(coopPoint));
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,16 @@
|
|||||||
|
package emu.grasscutter.server.packet.send;
|
||||||
|
|
||||||
|
import emu.grasscutter.net.packet.*;
|
||||||
|
import emu.grasscutter.net.proto.CancelCoopTaskRspOuterClass;
|
||||||
|
|
||||||
|
public class PacketCancelCoopTaskRsp extends BasePacket {
|
||||||
|
|
||||||
|
public PacketCancelCoopTaskRsp(int chapterId) {
|
||||||
|
super(PacketOpcodes.SetCoopChapterViewedRsp);
|
||||||
|
|
||||||
|
CancelCoopTaskRspOuterClass.CancelCoopTaskRsp proto =
|
||||||
|
CancelCoopTaskRspOuterClass.CancelCoopTaskRsp.newBuilder().setChapterId(chapterId).build();
|
||||||
|
|
||||||
|
this.setData(proto);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,49 @@
|
|||||||
|
package emu.grasscutter.server.packet.send;
|
||||||
|
|
||||||
|
import emu.grasscutter.data.GameData;
|
||||||
|
import emu.grasscutter.net.packet.*;
|
||||||
|
import emu.grasscutter.net.proto.CoopChapterOuterClass;
|
||||||
|
import emu.grasscutter.net.proto.CoopDataNotifyOuterClass;
|
||||||
|
import emu.grasscutter.net.proto.CoopPointOuterClass;
|
||||||
|
|
||||||
|
public class PacketCoopDataNotify extends BasePacket {
|
||||||
|
|
||||||
|
public PacketCoopDataNotify() {
|
||||||
|
super(PacketOpcodes.CoopDataNotify);
|
||||||
|
|
||||||
|
var proto = CoopDataNotifyOuterClass.CoopDataNotify.newBuilder();
|
||||||
|
proto.setIsHaveProgress(false);
|
||||||
|
|
||||||
|
// TODO: implement: determine the actual current progress point.
|
||||||
|
// Add every chapter and add the start point to each chapter regardless of actual progress.
|
||||||
|
GameData.getCoopChapterDataMap()
|
||||||
|
.values()
|
||||||
|
.forEach(
|
||||||
|
i -> {
|
||||||
|
var chapter = CoopChapterOuterClass.CoopChapter.newBuilder();
|
||||||
|
chapter.setId(i.getId());
|
||||||
|
|
||||||
|
// TODO: implement: look at unlockCond to determine what state each chapter should be
|
||||||
|
// in.
|
||||||
|
// Set every chapter to "Accept" regardless of accept conditions.
|
||||||
|
chapter.setStateValue(3); // 3 == STATE_ACCEPT
|
||||||
|
|
||||||
|
var point = CoopPointOuterClass.CoopPoint.newBuilder();
|
||||||
|
var pointList =
|
||||||
|
GameData.getCoopPointDataMap().values().stream()
|
||||||
|
.filter(
|
||||||
|
j -> j.getChapterId() == i.getId() && j.getType().equals("POINT_START"))
|
||||||
|
.toList();
|
||||||
|
|
||||||
|
if (!pointList.isEmpty()) {
|
||||||
|
int pointId = pointList.get(0).getId();
|
||||||
|
point.setId(pointId);
|
||||||
|
chapter.addCoopPointList(point);
|
||||||
|
}
|
||||||
|
|
||||||
|
proto.addChapterList(chapter);
|
||||||
|
});
|
||||||
|
|
||||||
|
this.setData(proto);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,16 @@
|
|||||||
|
package emu.grasscutter.server.packet.send;
|
||||||
|
|
||||||
|
import emu.grasscutter.net.packet.*;
|
||||||
|
import emu.grasscutter.net.proto.StartCoopPointRspOuterClass;
|
||||||
|
|
||||||
|
public class PacketStartCoopPointRsp extends BasePacket {
|
||||||
|
|
||||||
|
public PacketStartCoopPointRsp(int coopPoint) {
|
||||||
|
super(PacketOpcodes.StartCoopPointRsp);
|
||||||
|
|
||||||
|
StartCoopPointRspOuterClass.StartCoopPointRsp proto =
|
||||||
|
StartCoopPointRspOuterClass.StartCoopPointRsp.newBuilder().setCoopPoint(coopPoint).build();
|
||||||
|
|
||||||
|
this.setData(proto);
|
||||||
|
}
|
||||||
|
}
|
@ -10,6 +10,7 @@ import emu.grasscutter.data.common.ItemUseData;
|
|||||||
import emu.grasscutter.data.excels.*;
|
import emu.grasscutter.data.excels.*;
|
||||||
import emu.grasscutter.data.excels.achievement.AchievementData;
|
import emu.grasscutter.data.excels.achievement.AchievementData;
|
||||||
import emu.grasscutter.data.excels.avatar.AvatarData;
|
import emu.grasscutter.data.excels.avatar.AvatarData;
|
||||||
|
import emu.grasscutter.server.http.handlers.GachaHandler;
|
||||||
import emu.grasscutter.utils.*;
|
import emu.grasscutter.utils.*;
|
||||||
import emu.grasscutter.utils.lang.Language;
|
import emu.grasscutter.utils.lang.Language;
|
||||||
import emu.grasscutter.utils.lang.Language.TextStrings;
|
import emu.grasscutter.utils.lang.Language.TextStrings;
|
||||||
@ -311,8 +312,19 @@ public final class Tools {
|
|||||||
return sbs.stream().map(StringBuilder::toString).toList();
|
return sbs.stream().map(StringBuilder::toString).toList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void generateGachaMappings() {
|
||||||
|
var path = GachaHandler.getGachaMappingsPath();
|
||||||
|
if (!Files.exists(path)) {
|
||||||
|
try {
|
||||||
|
Grasscutter.getLogger().debug("Creating default '" + path + "' data");
|
||||||
|
Tools.createGachaMappings(path);
|
||||||
|
} catch (Exception exception) {
|
||||||
|
Grasscutter.getLogger().warn("Failed to create gacha mappings. \n" + exception);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static void createGachaMappings(Path location) throws IOException {
|
public static void createGachaMappings(Path location) throws IOException {
|
||||||
ResourceLoader.loadResources();
|
|
||||||
List<String> jsons = createGachaMappingJsons();
|
List<String> jsons = createGachaMappingJsons();
|
||||||
var usedLocales = new HashSet<String>();
|
var usedLocales = new HashSet<String>();
|
||||||
StringBuilder sb = new StringBuilder("mappings = {\n");
|
StringBuilder sb = new StringBuilder("mappings = {\n");
|
||||||
|
@ -1,42 +0,0 @@
|
|||||||
package emu.grasscutter.utils.objects;
|
|
||||||
|
|
||||||
import emu.grasscutter.Grasscutter;
|
|
||||||
import emu.grasscutter.Grasscutter.ServerRunMode;
|
|
||||||
import emu.grasscutter.database.*;
|
|
||||||
|
|
||||||
public interface DatabaseObject<T> {
|
|
||||||
/**
|
|
||||||
* @return Does this object belong in the game database?
|
|
||||||
*/
|
|
||||||
default boolean isGameObject() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return Should this object be saved immediately?
|
|
||||||
*/
|
|
||||||
default boolean saveImmediately() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Performs a deferred save.
|
|
||||||
* This object will save as a group with other objects.
|
|
||||||
*/
|
|
||||||
default void deferSave() {
|
|
||||||
Database.save(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Attempts to save this object to the database.
|
|
||||||
*/
|
|
||||||
default void save() {
|
|
||||||
if (this.isGameObject()) {
|
|
||||||
DatabaseManager.getGameDatastore().save(this);
|
|
||||||
} else if (Grasscutter.getRunMode() != ServerRunMode.GAME_ONLY) {
|
|
||||||
DatabaseManager.getAccountDatastore().save(this);
|
|
||||||
} else {
|
|
||||||
throw new UnsupportedOperationException("Unable to store an account object while in game-only mode.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,18 +1,60 @@
|
|||||||
package io.grasscutter;
|
package io.grasscutter;
|
||||||
|
|
||||||
import io.grasscutter.virtual.*;
|
import com.mchange.util.AssertException;
|
||||||
import lombok.*;
|
import emu.grasscutter.Grasscutter;
|
||||||
|
import emu.grasscutter.config.Configuration;
|
||||||
|
import java.io.IOException;
|
||||||
|
import lombok.Getter;
|
||||||
import okhttp3.OkHttpClient;
|
import okhttp3.OkHttpClient;
|
||||||
|
import okhttp3.Request;
|
||||||
|
import org.junit.jupiter.api.Assertions;
|
||||||
|
import org.junit.jupiter.api.BeforeAll;
|
||||||
|
import org.junit.jupiter.api.DisplayName;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
import java.util.concurrent.*;
|
/** Testing entrypoint for {@link Grasscutter}. */
|
||||||
|
|
||||||
public final class GrasscutterTest {
|
public final class GrasscutterTest {
|
||||||
@Getter
|
@Getter private static final OkHttpClient httpClient = new OkHttpClient();
|
||||||
public static final OkHttpClient httpClient = new OkHttpClient();
|
|
||||||
@Getter public static final ExecutorService executor = Executors.newSingleThreadExecutor();
|
|
||||||
|
|
||||||
@Getter public static VirtualAccount account;
|
@Getter private static int httpPort = -1;
|
||||||
@Setter
|
@Getter private static int gamePort = -1;
|
||||||
@Getter public static VirtualPlayer player;
|
|
||||||
@Getter public static VirtualGameSession gameSession;
|
/**
|
||||||
|
* Creates an HTTP URL.
|
||||||
|
*
|
||||||
|
* @param route The route to use.
|
||||||
|
* @return The URL.
|
||||||
|
*/
|
||||||
|
public static String http(String route) {
|
||||||
|
return "http://127.0.0.1:" + GrasscutterTest.httpPort + "/" + route;
|
||||||
|
}
|
||||||
|
|
||||||
|
@BeforeAll
|
||||||
|
public static void entry() {
|
||||||
|
try {
|
||||||
|
// Start Grasscutter.
|
||||||
|
Grasscutter.main(new String[] {"-test"});
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
throw new AssertException("Grasscutter failed to start.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the ports.
|
||||||
|
GrasscutterTest.httpPort = Configuration.SERVER.http.bindPort;
|
||||||
|
GrasscutterTest.gamePort = Configuration.SERVER.game.bindPort;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("HTTP server check")
|
||||||
|
public void checkHttpServer() {
|
||||||
|
// Create a request.
|
||||||
|
var request = new Request.Builder().url(GrasscutterTest.http("")).build();
|
||||||
|
|
||||||
|
// Perform the request.
|
||||||
|
try (var response = GrasscutterTest.httpClient.newCall(request).execute()) {
|
||||||
|
// Check the response.
|
||||||
|
Assertions.assertTrue(response.isSuccessful());
|
||||||
|
} catch (IOException exception) {
|
||||||
|
throw new AssertionError(exception);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,17 +0,0 @@
|
|||||||
package io.grasscutter;
|
|
||||||
|
|
||||||
import emu.grasscutter.utils.Utils;
|
|
||||||
import emu.grasscutter.utils.objects.Returnable;
|
|
||||||
|
|
||||||
public interface TestUtils {
|
|
||||||
/**
|
|
||||||
* Waits for a condition to be met.
|
|
||||||
*
|
|
||||||
* @param condition The condition.
|
|
||||||
*/
|
|
||||||
static void waitFor(Returnable<Boolean> condition) {
|
|
||||||
while (!condition.invoke()) {
|
|
||||||
Utils.sleep(100);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,61 +0,0 @@
|
|||||||
package io.grasscutter.tests;
|
|
||||||
|
|
||||||
import com.mchange.util.AssertException;
|
|
||||||
import emu.grasscutter.Grasscutter;
|
|
||||||
import emu.grasscutter.config.Configuration;
|
|
||||||
import io.grasscutter.GrasscutterTest;
|
|
||||||
import io.grasscutter.virtual.*;
|
|
||||||
import lombok.*;
|
|
||||||
import okhttp3.*;
|
|
||||||
import org.junit.jupiter.api.*;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
|
|
||||||
/** Testing entrypoint for {@link Grasscutter}. */
|
|
||||||
public final class BaseServerTest {
|
|
||||||
@Getter private static int httpPort = -1;
|
|
||||||
@Getter private static int gamePort = -1;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates an HTTP URL.
|
|
||||||
*
|
|
||||||
* @param route The route to use.
|
|
||||||
* @return The URL.
|
|
||||||
*/
|
|
||||||
public static String http(String route) {
|
|
||||||
return "http://127.0.0.1:" + BaseServerTest.httpPort + "/" + route;
|
|
||||||
}
|
|
||||||
|
|
||||||
@BeforeAll
|
|
||||||
public static void entry() {
|
|
||||||
try {
|
|
||||||
// Start Grasscutter.
|
|
||||||
Grasscutter.main(new String[] {"-test"});
|
|
||||||
} catch (Exception ignored) {
|
|
||||||
throw new AssertException("Grasscutter failed to start.");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set the ports.
|
|
||||||
BaseServerTest.httpPort = Configuration.SERVER.http.bindPort;
|
|
||||||
BaseServerTest.gamePort = Configuration.SERVER.game.bindPort;
|
|
||||||
|
|
||||||
// Create virtual instances.
|
|
||||||
GrasscutterTest.account = new VirtualAccount();
|
|
||||||
GrasscutterTest.gameSession = new VirtualGameSession();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@DisplayName("HTTP server check")
|
|
||||||
public void checkHttpServer() {
|
|
||||||
// Create a request.
|
|
||||||
var request = new Request.Builder().url(BaseServerTest.http("")).build();
|
|
||||||
|
|
||||||
// Perform the request.
|
|
||||||
try (var response = GrasscutterTest.httpClient.newCall(request).execute()) {
|
|
||||||
// Check the response.
|
|
||||||
Assertions.assertTrue(response.isSuccessful());
|
|
||||||
} catch (IOException exception) {
|
|
||||||
throw new AssertionError(exception);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,67 +0,0 @@
|
|||||||
package io.grasscutter.tests;
|
|
||||||
|
|
||||||
import emu.grasscutter.GameConstants;
|
|
||||||
import emu.grasscutter.net.packet.PacketOpcodes;
|
|
||||||
import emu.grasscutter.net.proto.*;
|
|
||||||
import emu.grasscutter.server.game.session.GameSessionManager;
|
|
||||||
import io.grasscutter.*;
|
|
||||||
import kcp.highway.*;
|
|
||||||
import lombok.Getter;
|
|
||||||
import org.junit.jupiter.api.*;
|
|
||||||
import org.junit.jupiter.api.MethodOrderer.OrderAnnotation;
|
|
||||||
|
|
||||||
@TestMethodOrder(OrderAnnotation.class)
|
|
||||||
public final class LoginTest {
|
|
||||||
@Getter private static final Ukcp KCP = new Ukcp(
|
|
||||||
null, null, null,
|
|
||||||
new ChannelConfig(), null);
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@Order(1)
|
|
||||||
@DisplayName("Connect to server")
|
|
||||||
public void connectToServer() {
|
|
||||||
var session = GrasscutterTest.getGameSession();
|
|
||||||
|
|
||||||
// Register the session.
|
|
||||||
GameSessionManager.getSessions().put(KCP, session);
|
|
||||||
|
|
||||||
// Try connecting to the server.
|
|
||||||
session.exchangeToken();
|
|
||||||
Assertions.assertTrue(session.waitForPacket(
|
|
||||||
PacketOpcodes.GetPlayerTokenRsp, 5));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@Order(2)
|
|
||||||
@DisplayName("Login to server")
|
|
||||||
public void loginToServer() {
|
|
||||||
var account = GrasscutterTest.getAccount();
|
|
||||||
var session = GrasscutterTest.getGameSession();
|
|
||||||
|
|
||||||
// Wait for the login response.
|
|
||||||
TestUtils.waitFor(session::useSecretKey);
|
|
||||||
|
|
||||||
// Send the login packet.
|
|
||||||
session.receive(
|
|
||||||
PacketOpcodes.PlayerLoginReq,
|
|
||||||
PlayerLoginReqOuterClass.PlayerLoginReq.newBuilder()
|
|
||||||
.setToken(account.getToken())
|
|
||||||
.build()
|
|
||||||
);
|
|
||||||
// Wait for the login response.
|
|
||||||
Assertions.assertTrue(session.waitForPacket(
|
|
||||||
PacketOpcodes.PlayerLoginRsp, 5));
|
|
||||||
|
|
||||||
// Send the born data request.
|
|
||||||
session.receive(
|
|
||||||
PacketOpcodes.SetPlayerBornDataReq,
|
|
||||||
SetPlayerBornDataReqOuterClass.SetPlayerBornDataReq.newBuilder()
|
|
||||||
.setAvatarId(GameConstants.MAIN_CHARACTER_FEMALE)
|
|
||||||
.setNickName("Virtual Player")
|
|
||||||
.build()
|
|
||||||
);
|
|
||||||
// Wait for the born data response.
|
|
||||||
Assertions.assertTrue(session.waitForPacket(
|
|
||||||
PacketOpcodes.SetPlayerBornDataRsp, 5));
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,23 +0,0 @@
|
|||||||
package io.grasscutter.virtual;
|
|
||||||
|
|
||||||
import emu.grasscutter.game.Account;
|
|
||||||
|
|
||||||
import java.util.Locale;
|
|
||||||
|
|
||||||
@SuppressWarnings("deprecation")
|
|
||||||
public final class VirtualAccount extends Account {
|
|
||||||
public VirtualAccount() {
|
|
||||||
super();
|
|
||||||
|
|
||||||
this.setId("virtual_account");
|
|
||||||
this.setUsername("virtual_account");
|
|
||||||
this.setPassword("virtual_account");
|
|
||||||
|
|
||||||
this.setReservedPlayerUid(10001);
|
|
||||||
this.setEmail("virtual_account@grasscutter.io");
|
|
||||||
this.setLocale(Locale.US);
|
|
||||||
|
|
||||||
this.generateSessionKey();
|
|
||||||
this.generateLoginToken();
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,142 +0,0 @@
|
|||||||
package io.grasscutter.virtual;
|
|
||||||
|
|
||||||
import com.google.protobuf.GeneratedMessageV3;
|
|
||||||
import emu.grasscutter.Grasscutter;
|
|
||||||
import emu.grasscutter.game.player.Player;
|
|
||||||
import emu.grasscutter.net.packet.*;
|
|
||||||
import emu.grasscutter.net.proto.GetPlayerTokenReqOuterClass.GetPlayerTokenReq;
|
|
||||||
import emu.grasscutter.net.proto.PacketHeadOuterClass.PacketHead;
|
|
||||||
import emu.grasscutter.server.game.GameSession;
|
|
||||||
import emu.grasscutter.utils.Crypto;
|
|
||||||
import io.grasscutter.GrasscutterTest;
|
|
||||||
import io.netty.buffer.Unpooled;
|
|
||||||
import org.slf4j.*;
|
|
||||||
|
|
||||||
import java.util.*;
|
|
||||||
import java.util.concurrent.*;
|
|
||||||
import java.util.function.Function;
|
|
||||||
|
|
||||||
public final class VirtualGameSession extends GameSession {
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger("Game Session");
|
|
||||||
|
|
||||||
private final Map<Integer, Set<Function<byte[], Boolean>>> listeners = new HashMap<>();
|
|
||||||
|
|
||||||
public VirtualGameSession() {
|
|
||||||
super(Grasscutter.getGameServer());
|
|
||||||
|
|
||||||
this.setAccount(GrasscutterTest.getAccount());
|
|
||||||
this.onConnected(new VirtualKcpTunnel());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Performs an exchange with the server for the player's token.
|
|
||||||
*/
|
|
||||||
public void exchangeToken() {
|
|
||||||
var account = GrasscutterTest.getAccount();
|
|
||||||
|
|
||||||
this.receive(
|
|
||||||
PacketOpcodes.GetPlayerTokenReq,
|
|
||||||
GetPlayerTokenReq.newBuilder()
|
|
||||||
.setAccountUid(account.getId())
|
|
||||||
.setAccountToken(account.getToken())
|
|
||||||
.build()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Registers a listener for a packet.
|
|
||||||
*
|
|
||||||
* @param packetId The packet's ID.
|
|
||||||
* @param listener The listener to register.
|
|
||||||
*/
|
|
||||||
public void addPacketListener(int packetId, Function<byte[], Boolean> listener) {
|
|
||||||
var listeners = this.listeners.computeIfAbsent(
|
|
||||||
packetId, k -> new HashSet<>());
|
|
||||||
listeners.add(listener);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Waits for a packet to be received.
|
|
||||||
*
|
|
||||||
* @param packetId The packet's ID.
|
|
||||||
* @param timeout The timeout in milliseconds.
|
|
||||||
*/
|
|
||||||
public boolean waitForPacket(int packetId, int timeout) {
|
|
||||||
var promise = new CompletableFuture<byte[]>();
|
|
||||||
this.addPacketListener(packetId, data -> {
|
|
||||||
promise.complete(data);
|
|
||||||
return false;
|
|
||||||
});
|
|
||||||
|
|
||||||
try {
|
|
||||||
promise.get(timeout, TimeUnit.SECONDS);
|
|
||||||
return true;
|
|
||||||
} catch (Exception e) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public synchronized void setPlayer(Player player) {
|
|
||||||
var newPlayer = new VirtualPlayer();
|
|
||||||
|
|
||||||
GrasscutterTest.setPlayer(newPlayer);
|
|
||||||
super.setPlayer(newPlayer);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Receives a packet from the client.
|
|
||||||
*
|
|
||||||
* @param packetId The packet's ID.
|
|
||||||
* @param message The packet to receive.
|
|
||||||
*/
|
|
||||||
public void receive(int packetId, GeneratedMessageV3 message) {
|
|
||||||
// Craft a packet header.
|
|
||||||
var header = PacketHead.newBuilder()
|
|
||||||
.setSentMs(System.currentTimeMillis())
|
|
||||||
.build();
|
|
||||||
// Serialize the message.
|
|
||||||
var headerBytes = header.toByteArray();
|
|
||||||
var messageBytes = message.toByteArray();
|
|
||||||
|
|
||||||
// Wrap the message into a packet.
|
|
||||||
var packet = Unpooled.buffer(12);
|
|
||||||
packet.writeShort(17767); // Packet header.
|
|
||||||
packet.writeShort(packetId); // Packet "opcode" or ID.
|
|
||||||
packet.writeShort(headerBytes.length); // Packet head length.
|
|
||||||
packet.writeInt(messageBytes.length); // Packet body length.
|
|
||||||
packet.writeBytes(headerBytes); // Packet head.
|
|
||||||
packet.writeBytes(messageBytes); // Packet body.
|
|
||||||
packet.writeShort(-30293); // Packet footer.
|
|
||||||
|
|
||||||
// Serialize the packet.
|
|
||||||
var data = packet.array();
|
|
||||||
// Encrypt the packet if specified.
|
|
||||||
Crypto.xor(data, this.useSecretKey() ?
|
|
||||||
Crypto.ENCRYPT_KEY : Crypto.DISPATCH_KEY);
|
|
||||||
|
|
||||||
// Dispatch the message to the server.
|
|
||||||
GrasscutterTest.getExecutor()
|
|
||||||
.submit(() -> this.onMessage(data));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void send(BasePacket packet) {
|
|
||||||
// Invoke packet handlers.
|
|
||||||
var listeners = this.listeners.get(packet.getOpcode());
|
|
||||||
if (listeners != null) {
|
|
||||||
var copy = new HashSet<>(listeners);
|
|
||||||
for (var listener : copy) {
|
|
||||||
if (listener.apply(packet.getData())) {
|
|
||||||
listeners.remove(listener);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Log the received packet.
|
|
||||||
logger.info("Received packet {} ({}) of length {} (header is {}).",
|
|
||||||
PacketOpcodesUtils.getOpcodeName(packet.getOpcode()), packet.getOpcode(),
|
|
||||||
packet.getData() == null ? "null" : packet.getData().length,
|
|
||||||
packet.getHeader() == null ? "null" : packet.getHeader().length);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,21 +0,0 @@
|
|||||||
package io.grasscutter.virtual;
|
|
||||||
|
|
||||||
import emu.grasscutter.net.KcpTunnel;
|
|
||||||
import java.net.InetSocketAddress;
|
|
||||||
|
|
||||||
public final class VirtualKcpTunnel implements KcpTunnel {
|
|
||||||
@Override
|
|
||||||
public InetSocketAddress getAddress() {
|
|
||||||
return new InetSocketAddress(1000);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void writeData(byte[] bytes) {
|
|
||||||
throw new UnsupportedOperationException("Cannot write to a virtual KCP tunnel");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void close() {
|
|
||||||
System.exit(0);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,13 +0,0 @@
|
|||||||
package io.grasscutter.virtual;
|
|
||||||
|
|
||||||
import emu.grasscutter.game.player.Player;
|
|
||||||
import io.grasscutter.GrasscutterTest;
|
|
||||||
import io.grasscutter.tests.BaseServerTest;
|
|
||||||
|
|
||||||
public final class VirtualPlayer extends Player {
|
|
||||||
public VirtualPlayer() {
|
|
||||||
super(GrasscutterTest.getGameSession());
|
|
||||||
|
|
||||||
this.setAccount(GrasscutterTest.getAccount());
|
|
||||||
}
|
|
||||||
}
|
|
Reference in New Issue
Block a user