Merge branch 'Grasscutters:development' into development

This commit is contained in:
loulou310 2022-06-15 16:26:10 +02:00 committed by GitHub
commit 584aec58a5
19 changed files with 245565 additions and 436 deletions

View File

@ -28,7 +28,7 @@ EN | [中文](README_zh-CN.md) | [FR](README_fr-FR.md)
**Note:** If you just want to **run it**, then **jre** only is fine. **Note:** If you just want to **run it**, then **jre** only is fine.
* MongoDB (recommended 4.0+) * [MongoDB](https://www.mongodb.com/try/download/community) (recommended 4.0+)
* Proxy daemon: mitmproxy (mitmdump, recommended), Fiddler Classic, etc. * Proxy daemon: mitmproxy (mitmdump, recommended), Fiddler Classic, etc.

View File

@ -28,7 +28,7 @@
**注:** 如果您仅仅想要简单地**运行服务端**, 那么使用 **jre** 便足够了 **注:** 如果您仅仅想要简单地**运行服务端**, 那么使用 **jre** 便足够了
* MongoDB (推荐 4.0+) * [MongoDB](https://fastdl.mongodb.org/windows/mongodb-windows-x86_64-5.0.9-signed.msi) (推荐 4.0+)
* Proxy daemon: mitmproxy (推荐使用mitmdump), Fiddler Classic, 等 * Proxy daemon: mitmproxy (推荐使用mitmdump), Fiddler Classic, 等

Binary file not shown.

BIN
lib/kcp.jar Normal file

Binary file not shown.

View File

@ -83,33 +83,33 @@ public final class DatabaseHelper {
} }
public static Account getAccountByName(String username) { public static Account getAccountByName(String username) {
return DatabaseManager.getGameDatastore().find(Account.class).filter(Filters.eq("username", username)).first(); return DatabaseManager.getAccountDatastore().find(Account.class).filter(Filters.eq("username", username)).first();
} }
public static Account getAccountByToken(String token) { public static Account getAccountByToken(String token) {
if(token == null) return null; if(token == null) return null;
return DatabaseManager.getGameDatastore().find(Account.class).filter(Filters.eq("token", token)).first(); return DatabaseManager.getAccountDatastore().find(Account.class).filter(Filters.eq("token", token)).first();
} }
public static Account getAccountBySessionKey(String sessionKey) { public static Account getAccountBySessionKey(String sessionKey) {
if(sessionKey == null) return null; if(sessionKey == null) return null;
return DatabaseManager.getGameDatastore().find(Account.class).filter(Filters.eq("sessionKey", sessionKey)).first(); return DatabaseManager.getAccountDatastore().find(Account.class).filter(Filters.eq("sessionKey", sessionKey)).first();
} }
public static Account getAccountById(String uid) { public static Account getAccountById(String uid) {
return DatabaseManager.getGameDatastore().find(Account.class).filter(Filters.eq("_id", uid)).first(); return DatabaseManager.getAccountDatastore().find(Account.class).filter(Filters.eq("_id", uid)).first();
} }
public static Account getAccountByPlayerId(int playerId) { public static Account getAccountByPlayerId(int playerId) {
return DatabaseManager.getGameDatastore().find(Account.class).filter(Filters.eq("reservedPlayerId", playerId)).first(); return DatabaseManager.getAccountDatastore().find(Account.class).filter(Filters.eq("reservedPlayerId", playerId)).first();
} }
public static boolean checkIfAccountExists(String name) { public static boolean checkIfAccountExists(String name) {
return DatabaseManager.getGameDatastore().find(Account.class).filter(Filters.eq("username", name)).count() > 0; return DatabaseManager.getAccountDatastore().find(Account.class).filter(Filters.eq("username", name)).count() > 0;
} }
public static boolean checkIfAccountExists(int reservedUid) { public static boolean checkIfAccountExists(int reservedUid) {
return DatabaseManager.getGameDatastore().find(Account.class).filter(Filters.eq("reservedPlayerId", reservedUid)).count() > 0; return DatabaseManager.getAccountDatastore().find(Account.class).filter(Filters.eq("reservedPlayerId", reservedUid)).count() > 0;
} }
public static void deleteAccount(Account target) { public static void deleteAccount(Account target) {
@ -141,7 +141,7 @@ public final class DatabaseHelper {
} }
// Finally, delete the account itself. // Finally, delete the account itself.
DatabaseManager.getGameDatastore().find(Account.class).filter(Filters.eq("id", target.getId())).delete(); DatabaseManager.getAccountDatastore().find(Account.class).filter(Filters.eq("id", target.getId())).delete();
} }
public static List<Player> getAllPlayers() { public static List<Player> getAllPlayers() {

View File

@ -16,6 +16,7 @@ import emu.grasscutter.net.proto.AbilityControlBlockOuterClass.AbilityControlBlo
import emu.grasscutter.net.proto.AbilityEmbryoOuterClass.AbilityEmbryo; import emu.grasscutter.net.proto.AbilityEmbryoOuterClass.AbilityEmbryo;
import emu.grasscutter.net.proto.AbilitySyncStateInfoOuterClass.AbilitySyncStateInfo; import emu.grasscutter.net.proto.AbilitySyncStateInfoOuterClass.AbilitySyncStateInfo;
import emu.grasscutter.net.proto.AnimatorParameterValueInfoPairOuterClass.AnimatorParameterValueInfoPair; import emu.grasscutter.net.proto.AnimatorParameterValueInfoPairOuterClass.AnimatorParameterValueInfoPair;
import emu.grasscutter.net.proto.ChangeEnergyReasonOuterClass.ChangeEnergyReason;
import emu.grasscutter.net.proto.ChangeHpReasonOuterClass.ChangeHpReason; import emu.grasscutter.net.proto.ChangeHpReasonOuterClass.ChangeHpReason;
import emu.grasscutter.net.proto.EntityAuthorityInfoOuterClass.EntityAuthorityInfo; import emu.grasscutter.net.proto.EntityAuthorityInfoOuterClass.EntityAuthorityInfo;
import emu.grasscutter.net.proto.EntityClientDataOuterClass.EntityClientData; import emu.grasscutter.net.proto.EntityClientDataOuterClass.EntityClientData;
@ -31,6 +32,7 @@ import emu.grasscutter.net.proto.SceneEntityInfoOuterClass.SceneEntityInfo;
import emu.grasscutter.net.proto.VectorOuterClass.Vector; import emu.grasscutter.net.proto.VectorOuterClass.Vector;
import emu.grasscutter.server.packet.send.PacketAvatarFightPropUpdateNotify; import emu.grasscutter.server.packet.send.PacketAvatarFightPropUpdateNotify;
import emu.grasscutter.server.packet.send.PacketEntityFightPropChangeReasonNotify; import emu.grasscutter.server.packet.send.PacketEntityFightPropChangeReasonNotify;
import emu.grasscutter.server.packet.send.PacketEntityFightPropUpdateNotify;
import emu.grasscutter.utils.Position; import emu.grasscutter.utils.Position;
import emu.grasscutter.utils.ProtoHelper; import emu.grasscutter.utils.ProtoHelper;
import emu.grasscutter.utils.Utils; import emu.grasscutter.utils.Utils;
@ -108,13 +110,13 @@ public class EntityAvatar extends GameEntity {
public void onDeath(int killerId) { public void onDeath(int killerId) {
this.killedType = PlayerDieType.PLAYER_DIE_TYPE_KILL_BY_MONSTER; this.killedType = PlayerDieType.PLAYER_DIE_TYPE_KILL_BY_MONSTER;
this.killedBy = killerId; this.killedBy = killerId;
clearEnergy(PropChangeReason.PROP_CHANGE_REASON_STATUE_RECOVER); clearEnergy(ChangeEnergyReason.CHANGE_ENERGY_REASON_NONE);
} }
public void onDeath(PlayerDieType dieType, int killerId) { public void onDeath(PlayerDieType dieType, int killerId) {
this.killedType = dieType; this.killedType = dieType;
this.killedBy = killerId; this.killedBy = killerId;
clearEnergy(PropChangeReason.PROP_CHANGE_REASON_STATUE_RECOVER); clearEnergy(ChangeEnergyReason.CHANGE_ENERGY_REASON_NONE);
} }
@Override @Override
@ -130,14 +132,25 @@ public class EntityAvatar extends GameEntity {
return healed; return healed;
} }
public void clearEnergy(PropChangeReason reason) { public void clearEnergy(ChangeEnergyReason reason) {
// Fight props.
FightProperty curEnergyProp = this.getAvatar().getSkillDepot().getElementType().getCurEnergyProp(); FightProperty curEnergyProp = this.getAvatar().getSkillDepot().getElementType().getCurEnergyProp();
this.avatar.setCurrentEnergy(curEnergyProp, 0); FightProperty maxEnergyProp = this.getAvatar().getSkillDepot().getElementType().getMaxEnergyProp();
this.getScene().broadcastPacket(new PacketAvatarFightPropUpdateNotify(this.getAvatar(), curEnergyProp));
this.getScene().broadcastPacket(new PacketEntityFightPropChangeReasonNotify(this, curEnergyProp, 0f, reason));
}
// Get max energy.
float maxEnergy = this.avatar.getFightProperty(maxEnergyProp);
// Set energy to zero.
this.avatar.setCurrentEnergy(curEnergyProp, 0);
// Send packets.
this.getScene().broadcastPacket(new PacketEntityFightPropUpdateNotify(this, curEnergyProp));
if (reason == ChangeEnergyReason.CHANGE_ENERGY_REASON_SKILL_START) {
this.getScene().broadcastPacket(new PacketEntityFightPropChangeReasonNotify(this, curEnergyProp, -maxEnergy, reason));
}
}
public void addEnergy(float amount, PropChangeReason reason) { public void addEnergy(float amount, PropChangeReason reason) {
this.addEnergy(amount, reason, false); this.addEnergy(amount, reason, false);
} }

View File

@ -31,6 +31,7 @@ import emu.grasscutter.net.proto.GachaItemOuterClass.GachaItem;
import emu.grasscutter.net.proto.GachaTransferItemOuterClass.GachaTransferItem; import emu.grasscutter.net.proto.GachaTransferItemOuterClass.GachaTransferItem;
import emu.grasscutter.net.proto.GetGachaInfoRspOuterClass.GetGachaInfoRsp; import emu.grasscutter.net.proto.GetGachaInfoRspOuterClass.GetGachaInfoRsp;
import emu.grasscutter.net.proto.ItemParamOuterClass.ItemParam; import emu.grasscutter.net.proto.ItemParamOuterClass.ItemParam;
import emu.grasscutter.net.proto.RetcodeOuterClass.Retcode;
import emu.grasscutter.server.game.GameServer; import emu.grasscutter.server.game.GameServer;
import emu.grasscutter.server.game.GameServerTickEvent; import emu.grasscutter.server.game.GameServerTickEvent;
import emu.grasscutter.server.packet.send.PacketDoGachaRsp; import emu.grasscutter.server.packet.send.PacketDoGachaRsp;
@ -244,7 +245,7 @@ public class GachaManager {
} }
Inventory inventory = player.getInventory(); Inventory inventory = player.getInventory();
if (inventory.getInventoryTab(ItemType.ITEM_WEAPON).getSize() + times > inventory.getInventoryTab(ItemType.ITEM_WEAPON).getMaxCapacity()) { if (inventory.getInventoryTab(ItemType.ITEM_WEAPON).getSize() + times > inventory.getInventoryTab(ItemType.ITEM_WEAPON).getMaxCapacity()) {
player.sendPacket(new PacketDoGachaRsp()); player.sendPacket(new PacketDoGachaRsp(Retcode.RET_ITEM_EXCEED_LIMIT));
return; return;
} }

View File

@ -22,6 +22,7 @@ import emu.grasscutter.net.proto.AbilityActionGenerateElemBallOuterClass.Ability
import emu.grasscutter.net.proto.AbilityIdentifierOuterClass.AbilityIdentifier; import emu.grasscutter.net.proto.AbilityIdentifierOuterClass.AbilityIdentifier;
import emu.grasscutter.net.proto.AbilityInvokeEntryOuterClass.AbilityInvokeEntry; import emu.grasscutter.net.proto.AbilityInvokeEntryOuterClass.AbilityInvokeEntry;
import emu.grasscutter.net.proto.AttackResultOuterClass.AttackResult; import emu.grasscutter.net.proto.AttackResultOuterClass.AttackResult;
import emu.grasscutter.net.proto.ChangeEnergyReasonOuterClass.ChangeEnergyReason;
import emu.grasscutter.net.proto.EvtBeingHitInfoOuterClass.EvtBeingHitInfo; import emu.grasscutter.net.proto.EvtBeingHitInfoOuterClass.EvtBeingHitInfo;
import emu.grasscutter.net.proto.PropChangeReasonOuterClass.PropChangeReason; import emu.grasscutter.net.proto.PropChangeReasonOuterClass.PropChangeReason;
import emu.grasscutter.server.game.GameSession; import emu.grasscutter.server.game.GameSession;
@ -334,7 +335,7 @@ public class EnergyManager {
// If the cast skill was a burst, consume energy. // If the cast skill was a burst, consume energy.
if (avatar.getSkillDepot() != null && skillId == avatar.getSkillDepot().getEnergySkill()) { if (avatar.getSkillDepot() != null && skillId == avatar.getSkillDepot().getEnergySkill()) {
avatar.getAsEntity().clearEnergy(PropChangeReason.PROP_CHANGE_REASON_ABILITY); avatar.getAsEntity().clearEnergy(ChangeEnergyReason.CHANGE_ENERGY_REASON_SKILL_START);
} }
} }

View File

@ -1,89 +0,0 @@
package emu.grasscutter.netty;
import emu.grasscutter.Grasscutter;
import io.jpower.kcp.netty.UkcpChannel;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufUtil;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
public abstract class KcpChannel extends ChannelInboundHandlerAdapter {
private UkcpChannel kcpChannel;
private ChannelHandlerContext ctx;
private boolean isActive;
public UkcpChannel getChannel() {
return kcpChannel;
}
public boolean isActive() {
return this.isActive;
}
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
this.kcpChannel = (UkcpChannel) ctx.channel();
this.ctx = ctx;
this.isActive = true;
this.onConnect();
}
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
this.isActive = false;
this.onDisconnect();
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
ByteBuf data = (ByteBuf) msg;
onMessage(ctx, data);
}
@Override
public void channelReadComplete(ChannelHandlerContext ctx) {
ctx.flush();
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
cause.printStackTrace();
close();
}
protected void send(byte[] data) {
if (!isActive()) {
return;
}
ByteBuf packet = Unpooled.wrappedBuffer(data);
kcpChannel.writeAndFlush(packet);
}
public void close() {
if (getChannel() != null) {
getChannel().close();
}
}
/*
protected void logPacket(ByteBuffer buf) {
ByteBuf b = Unpooled.wrappedBuffer(buf.array());
logPacket(b);
}
*/
protected void logPacket(ByteBuf buf) {
Grasscutter.getLogger().info("Received: \n" + ByteBufUtil.prettyHexDump(buf));
}
// Events
protected abstract void onConnect();
protected abstract void onDisconnect();
public abstract void onMessage(ChannelHandlerContext ctx, ByteBuf data);
}

View File

@ -1,85 +0,0 @@
package emu.grasscutter.netty;
import java.net.SocketAddress;
import java.nio.channels.SelectableChannel;
import java.util.List;
import io.netty.channel.Channel;
import io.netty.channel.ChannelConfig;
import io.netty.channel.ChannelMetadata;
import io.netty.channel.ChannelOutboundBuffer;
import io.netty.channel.nio.AbstractNioMessageChannel;
public class KcpHandshaker extends AbstractNioMessageChannel {
protected KcpHandshaker(Channel parent, SelectableChannel ch, int readInterestOp) {
super(parent, ch, readInterestOp);
}
@Override
public ChannelConfig config() {
// TODO Auto-generated method stub
return null;
}
@Override
public boolean isActive() {
// TODO Auto-generated method stub
return false;
}
@Override
public ChannelMetadata metadata() {
// TODO Auto-generated method stub
return null;
}
@Override
protected int doReadMessages(List<Object> buf) throws Exception {
// TODO Auto-generated method stub
return 0;
}
@Override
protected boolean doWriteMessage(Object msg, ChannelOutboundBuffer in) throws Exception {
// TODO Auto-generated method stub
return false;
}
@Override
protected boolean doConnect(SocketAddress remoteAddress, SocketAddress localAddress) throws Exception {
// TODO Auto-generated method stub
return false;
}
@Override
protected void doFinishConnect() throws Exception {
// TODO Auto-generated method stub
}
@Override
protected SocketAddress localAddress0() {
// TODO Auto-generated method stub
return null;
}
@Override
protected SocketAddress remoteAddress0() {
// TODO Auto-generated method stub
return null;
}
@Override
protected void doBind(SocketAddress localAddress) throws Exception {
// TODO Auto-generated method stub
}
@Override
protected void doDisconnect() throws Exception {
// TODO Auto-generated method stub
}
}

View File

@ -1,93 +0,0 @@
package emu.grasscutter.netty;
import java.net.InetSocketAddress;
import emu.grasscutter.Grasscutter;
import io.jpower.kcp.netty.ChannelOptionHelper;
import io.jpower.kcp.netty.UkcpChannelOption;
import io.jpower.kcp.netty.UkcpServerChannel;
import io.netty.bootstrap.UkcpServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
@SuppressWarnings("rawtypes")
public class KcpServer extends Thread {
private EventLoopGroup group;
private UkcpServerBootstrap bootstrap;
private ChannelInitializer serverInitializer;
private InetSocketAddress address;
public KcpServer(InetSocketAddress address) {
this.address = address;
this.setName("Netty Server Thread");
}
public InetSocketAddress getAddress() {
return this.address;
}
public ChannelInitializer getServerInitializer() {
return serverInitializer;
}
public void setServerInitializer(ChannelInitializer serverInitializer) {
this.serverInitializer = serverInitializer;
}
@Override
public void run() {
if (getServerInitializer() == null) {
this.setServerInitializer(new KcpServerInitializer());
}
try {
group = new NioEventLoopGroup();
bootstrap = new UkcpServerBootstrap();
bootstrap.group(group)
.channel(UkcpServerChannel.class)
.childHandler(this.getServerInitializer());
ChannelOptionHelper
.nodelay(bootstrap, true, 20, 2, true)
.childOption(UkcpChannelOption.UKCP_MTU, 1200);
// Start handler
this.onStart();
// Start the server.
ChannelFuture f = bootstrap.bind(getAddress()).sync();
// Start finish handler
this.onStartFinish();
// Wait until the server socket is closed.
f.channel().closeFuture().sync();
} catch (Exception exception) {
Grasscutter.getLogger().error("Unable to start game server.", exception);
} finally {
// Close
finish();
}
}
public void onStart() {
}
public void onStartFinish() {
}
private void finish() {
try {
group.shutdownGracefully();
} catch (Exception e) {
}
Grasscutter.getLogger().info("Game Server closed");
}
}

View File

@ -1,15 +0,0 @@
package emu.grasscutter.netty;
import io.jpower.kcp.netty.UkcpChannel;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
@SuppressWarnings("unused")
public class KcpServerInitializer extends ChannelInitializer<UkcpChannel> {
@Override
protected void initChannel(UkcpChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
}
}

View File

@ -21,13 +21,13 @@ import emu.grasscutter.game.tower.TowerScheduleManager;
import emu.grasscutter.game.world.World; import emu.grasscutter.game.world.World;
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.netty.KcpServer;
import emu.grasscutter.server.event.types.ServerEvent; import emu.grasscutter.server.event.types.ServerEvent;
import emu.grasscutter.server.event.game.ServerTickEvent; import emu.grasscutter.server.event.game.ServerTickEvent;
import emu.grasscutter.server.event.internal.ServerStartEvent; import emu.grasscutter.server.event.internal.ServerStartEvent;
import emu.grasscutter.server.event.internal.ServerStopEvent; import emu.grasscutter.server.event.internal.ServerStopEvent;
import emu.grasscutter.task.TaskMap; import emu.grasscutter.task.TaskMap;
import emu.grasscutter.BuildConfig; import kcp.highway.ChannelConfig;
import kcp.highway.KcpServer;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.time.OffsetDateTime; import java.time.OffsetDateTime;
@ -60,7 +60,7 @@ public final class GameServer extends KcpServer {
private final TowerScheduleManager towerScheduleManager; private final TowerScheduleManager towerScheduleManager;
private static InetSocketAddress getAdapterInetSocketAddress(){ private static InetSocketAddress getAdapterInetSocketAddress(){
InetSocketAddress inetSocketAddress = null; InetSocketAddress inetSocketAddress;
if(GAME_INFO.bindAddress.equals("")){ if(GAME_INFO.bindAddress.equals("")){
inetSocketAddress=new InetSocketAddress(GAME_INFO.bindPort); inetSocketAddress=new InetSocketAddress(GAME_INFO.bindPort);
}else{ }else{
@ -75,9 +75,17 @@ public final class GameServer extends KcpServer {
this(getAdapterInetSocketAddress()); this(getAdapterInetSocketAddress());
} }
public GameServer(InetSocketAddress address) { public GameServer(InetSocketAddress address) {
super(address); ChannelConfig channelConfig = new ChannelConfig();
channelConfig.nodelay(true,40,2,true);
channelConfig.setMtu(1400);
channelConfig.setSndwnd(256);
channelConfig.setRcvwnd(256);
channelConfig.setTimeoutMillis(30*1000);//30s
channelConfig.setUseConvChannel(true);
channelConfig.setAckNoDelay(false);
this.init(GameSessionManager.getListener(),channelConfig,address);
this.setServerInitializer(new GameServerInitializer(this));
this.address = address; this.address = address;
this.packetHandler = new GameServerPacketHandler(PacketHandler.class); this.packetHandler = new GameServerPacketHandler(PacketHandler.class);
this.questHandler = new ServerQuestHandler(); this.questHandler = new ServerQuestHandler();
@ -96,6 +104,7 @@ public final class GameServer extends KcpServer {
this.expeditionManager = new ExpeditionManager(this); this.expeditionManager = new ExpeditionManager(this);
this.combineManger = new CombineManger(this); this.combineManger = new CombineManger(this);
this.towerScheduleManager = new TowerScheduleManager(this); this.towerScheduleManager = new TowerScheduleManager(this);
// Hook into shutdown event. // Hook into shutdown event.
Runtime.getRuntime().addShutdownHook(new Thread(this::onServerShutdown)); Runtime.getRuntime().addShutdownHook(new Thread(this::onServerShutdown));
} }
@ -164,6 +173,7 @@ public final class GameServer extends KcpServer {
return towerScheduleManager; return towerScheduleManager;
} }
public TaskMap getTaskMap() { public TaskMap getTaskMap() {
return this.taskMap; return this.taskMap;
} }
@ -220,23 +230,23 @@ public final class GameServer extends KcpServer {
} }
return DatabaseHelper.getAccountByName(username); return DatabaseHelper.getAccountByName(username);
} }
public void onTick() throws Exception { public synchronized void onTick(){
Iterator<World> it = this.getWorlds().iterator(); Iterator<World> it = this.getWorlds().iterator();
while (it.hasNext()) { while (it.hasNext()) {
World world = it.next(); World world = it.next();
if (world.getPlayerCount() == 0) { if (world.getPlayerCount() == 0) {
it.remove(); it.remove();
} }
world.onTick(); world.onTick();
} }
for (Player player : this.getPlayers().values()) { for (Player player : this.getPlayers().values()) {
player.onTick(); player.onTick();
} }
ServerTickEvent event = new ServerTickEvent(); event.call(); ServerTickEvent event = new ServerTickEvent(); event.call();
} }
@ -249,8 +259,7 @@ public final class GameServer extends KcpServer {
} }
@Override public void start() {
public synchronized void start() {
// Schedule game loop. // Schedule game loop.
Timer gameLoop = new Timer(); Timer gameLoop = new Timer();
gameLoop.scheduleAtFixedRate(new TimerTask() { gameLoop.scheduleAtFixedRate(new TimerTask() {
@ -263,24 +272,19 @@ public final class GameServer extends KcpServer {
} }
} }
}, new Date(), 1000L); }, new Date(), 1000L);
super.start();
}
@Override
public void onStartFinish() {
Grasscutter.getLogger().info(translate("messages.status.free_software")); Grasscutter.getLogger().info(translate("messages.status.free_software"));
Grasscutter.getLogger().info(translate("messages.game.port_bind", Integer.toString(address.getPort()))); Grasscutter.getLogger().info(translate("messages.game.port_bind", Integer.toString(address.getPort())));
ServerStartEvent event = new ServerStartEvent(ServerEvent.Type.GAME, OffsetDateTime.now()); event.call(); ServerStartEvent event = new ServerStartEvent(ServerEvent.Type.GAME, OffsetDateTime.now());
event.call();
} }
public void onServerShutdown() { public void onServerShutdown() {
ServerStopEvent event = new ServerStopEvent(ServerEvent.Type.GAME, OffsetDateTime.now()); event.call(); ServerStopEvent event = new ServerStopEvent(ServerEvent.Type.GAME, OffsetDateTime.now()); event.call();
// Kick and save all players // Kick and save all players
List<Player> list = new ArrayList<>(this.getPlayers().size()); List<Player> list = new ArrayList<>(this.getPlayers().size());
list.addAll(this.getPlayers().values()); list.addAll(this.getPlayers().values());
for (Player player : list) { for (Player player : list) {
player.getSession().close(); player.getSession().close();
} }

View File

@ -1,22 +0,0 @@
package emu.grasscutter.server.game;
import emu.grasscutter.netty.KcpServerInitializer;
import io.jpower.kcp.netty.UkcpChannel;
import io.netty.channel.ChannelPipeline;
public class GameServerInitializer extends KcpServerInitializer {
private GameServer server;
public GameServerInitializer(GameServer server) {
this.server = server;
}
@Override
protected void initChannel(UkcpChannel ch) throws Exception {
ChannelPipeline pipeline=null;
if(ch!=null){
pipeline = ch.pipeline();
}
new GameSession(server,pipeline);
}
}

View File

@ -2,9 +2,6 @@ package emu.grasscutter.server.game;
import java.io.File; import java.io.File;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.util.Iterator;
import java.util.Map;
import java.util.Set; import java.util.Set;
import emu.grasscutter.Grasscutter; import emu.grasscutter.Grasscutter;
@ -14,23 +11,19 @@ import emu.grasscutter.game.player.Player;
import emu.grasscutter.net.packet.BasePacket; import emu.grasscutter.net.packet.BasePacket;
import emu.grasscutter.net.packet.PacketOpcodes; import emu.grasscutter.net.packet.PacketOpcodes;
import emu.grasscutter.net.packet.PacketOpcodesUtil; import emu.grasscutter.net.packet.PacketOpcodesUtil;
import emu.grasscutter.netty.KcpChannel;
import emu.grasscutter.server.event.game.SendPacketEvent; import emu.grasscutter.server.event.game.SendPacketEvent;
import emu.grasscutter.utils.Crypto; import emu.grasscutter.utils.Crypto;
import emu.grasscutter.utils.FileUtils; import emu.grasscutter.utils.FileUtils;
import emu.grasscutter.utils.Utils; import emu.grasscutter.utils.Utils;
import io.jpower.kcp.netty.UkcpChannel;
import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled; import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelPipeline;
import static emu.grasscutter.utils.Language.translate;
import static emu.grasscutter.Configuration.*; import static emu.grasscutter.Configuration.*;
import static emu.grasscutter.utils.Language.translate;
public class GameSession extends KcpChannel { public class GameSession implements GameSessionManager.KcpChannel {
private final GameServer server; private final GameServer server;
private GameSessionManager.KcpTunnel tunnel;
private Account account; private Account account;
private Player player; private Player player;
@ -41,29 +34,10 @@ public class GameSession extends KcpChannel {
private long lastPingTime; private long lastPingTime;
private int lastClientSeq = 10; private int lastClientSeq = 10;
private final ChannelPipeline pipeline;
@Override
public void close() {
setState(SessionState.INACTIVE);
//send disconnection pack in case of reconnection
try {
send(new BasePacket(PacketOpcodes.ServerDisconnectClientNotify));
}catch (Throwable ignore){
}
super.close();
}
public GameSession(GameServer server) { public GameSession(GameServer server) {
this(server,null);
}
public GameSession(GameServer server, ChannelPipeline pipeline) {
this.server = server; this.server = server;
this.state = SessionState.WAITING_FOR_TOKEN; this.state = SessionState.WAITING_FOR_TOKEN;
this.lastPingTime = System.currentTimeMillis(); this.lastPingTime = System.currentTimeMillis();
this.pipeline = pipeline;
if(pipeline!=null) {
pipeline.addLast(this);
}
} }
public GameServer getServer() { public GameServer getServer() {
@ -71,10 +45,11 @@ public class GameSession extends KcpChannel {
} }
public InetSocketAddress getAddress() { public InetSocketAddress getAddress() {
if (this.getChannel() == null) { try{
return tunnel.getAddress();
}catch (Throwable ignore){
return null; return null;
} }
return this.getChannel().remoteAddress();
} }
public boolean useSecretKey() { public boolean useSecretKey() {
@ -135,37 +110,7 @@ public class GameSession extends KcpChannel {
public int getNextClientSequence() { public int getNextClientSequence() {
return ++lastClientSeq; return ++lastClientSeq;
} }
@Override
protected void onConnect() {
Grasscutter.getLogger().info(translate("messages.game.connect", this.getAddress().getHostString().toLowerCase()));
}
@Override
protected synchronized void onDisconnect() { // Synchronize so we don't add character at the same time.
Grasscutter.getLogger().info(translate("messages.game.disconnect", this.getAddress().getHostString().toLowerCase()));
// Set state so no more packets can be handled
this.setState(SessionState.INACTIVE);
// Save after disconnecting
if (this.isLoggedIn()) {
Player player = getPlayer();
// Call logout event.
player.onLogout();
}
try {
pipeline.remove(this);
} catch (Throwable ignore) {
}
}
protected void logPacket(ByteBuffer buf) {
ByteBuf b = Unpooled.wrappedBuffer(buf.array());
logPacket(b);
}
public void replayPacket(int opcode, String name) { public void replayPacket(int opcode, String name) {
String filePath = PACKET(name); String filePath = PACKET(name);
File p = new File(filePath); File p = new File(filePath);
@ -200,13 +145,16 @@ public class GameSession extends KcpChannel {
// Log // Log
if (SERVER.debugLevel == ServerDebugMode.ALL) { if (SERVER.debugLevel == ServerDebugMode.ALL) {
logPacket(packet); if (!loopPacket.contains(packet.getOpcode())) {
Grasscutter.getLogger().info("SEND: " + PacketOpcodesUtil.getOpcodeName(packet.getOpcode()) + " (" + packet.getOpcode() + ")");
System.out.println(Utils.bytesToHex(packet.getData()));
}
} }
// Invoke event. // Invoke event.
SendPacketEvent event = new SendPacketEvent(this, packet); event.call(); SendPacketEvent event = new SendPacketEvent(this, packet); event.call();
if(!event.isCanceled()) // If event is not cancelled, continue. if(!event.isCanceled()) { // If event is not cancelled, continue.
this.send(event.getPacket().build()); tunnel.writeData(event.getPacket().build());
}
} }
private static final Set<Integer> loopPacket = Set.of( private static final Set<Integer> loopPacket = Set.of(
@ -217,78 +165,104 @@ public class GameSession extends KcpChannel {
PacketOpcodes.QueryPathReq PacketOpcodes.QueryPathReq
); );
private void logPacket(BasePacket packet) { @Override
if (!loopPacket.contains(packet.getOpcode())) { public void onConnected(GameSessionManager.KcpTunnel tunnel) {
Grasscutter.getLogger().info("SEND: " + PacketOpcodesUtil.getOpcodeName(packet.getOpcode()) + " (" + packet.getOpcode() + ")"); this.tunnel = tunnel;
System.out.println(Utils.bytesToHex(packet.getData())); Grasscutter.getLogger().info(translate("messages.game.connect", this.getAddress().toString()));
} }
}
@Override @Override
public void onMessage(ChannelHandlerContext ctx, ByteBuf data) { public void handleReceive(byte[] bytes) {
// Decrypt and turn back into a packet // Decrypt and turn back into a packet
byte[] byteData = Utils.byteBufToArray(data); Crypto.xor(bytes, useSecretKey() ? Crypto.ENCRYPT_KEY : Crypto.DISPATCH_KEY);
Crypto.xor(byteData, useSecretKey() ? Crypto.ENCRYPT_KEY : Crypto.DISPATCH_KEY); ByteBuf packet = Unpooled.wrappedBuffer(bytes);
ByteBuf packet = Unpooled.wrappedBuffer(byteData);
// Log // Log
//logPacket(packet); //logPacket(packet);
// Handle // Handle
try { try {
boolean allDebug = SERVER.debugLevel == ServerDebugMode.ALL;
while (packet.readableBytes() > 0) { while (packet.readableBytes() > 0) {
// Length // Length
if (packet.readableBytes() < 12) { if (packet.readableBytes() < 12) {
return; return;
} }
// Packet sanity check // Packet sanity check
int const1 = packet.readShort(); int const1 = packet.readShort();
if (const1 != 17767) { if (const1 != 17767) {
if(allDebug){
Grasscutter.getLogger().error("Bad Data Package Received: got {} ,expect 17767",const1);
}
return; // Bad packet return; // Bad packet
} }
// Data // Data
int opcode = packet.readShort(); int opcode = packet.readShort();
int headerLength = packet.readShort(); int headerLength = packet.readShort();
int payloadLength = packet.readInt(); int payloadLength = packet.readInt();
byte[] header = new byte[headerLength]; byte[] header = new byte[headerLength];
byte[] payload = new byte[payloadLength]; byte[] payload = new byte[payloadLength];
packet.readBytes(header); packet.readBytes(header);
packet.readBytes(payload); packet.readBytes(payload);
// Sanity check #2 // Sanity check #2
int const2 = packet.readShort(); int const2 = packet.readShort();
if (const2 != -30293) { if (const2 != -30293) {
if(allDebug){
Grasscutter.getLogger().error("Bad Data Package Received: got {} ,expect -30293",const2);
}
return; // Bad packet return; // Bad packet
} }
// Log packet // Log packet
if (SERVER.debugLevel == ServerDebugMode.ALL) { if (allDebug) {
if (!loopPacket.contains(opcode)) { if (!loopPacket.contains(opcode)) {
Grasscutter.getLogger().info("RECV: " + PacketOpcodesUtil.getOpcodeName(opcode) + " (" + opcode + ")"); Grasscutter.getLogger().info("RECV: " + PacketOpcodesUtil.getOpcodeName(opcode) + " (" + opcode + ")");
System.out.println(Utils.bytesToHex(payload)); System.out.println(Utils.bytesToHex(payload));
} }
} }
// Handle // Handle
getServer().getPacketHandler().handle(this, opcode, header, payload); getServer().getPacketHandler().handle(this, opcode, header, payload);
} }
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); e.printStackTrace();
} finally { } finally {
data.release(); //byteBuf.release(); //Needn't
packet.release(); packet.release();
} }
} }
@Override
public void handleClose() {
setState(SessionState.INACTIVE);
//send disconnection pack in case of reconnection
Grasscutter.getLogger().info(translate("messages.game.disconnect", this.getAddress().toString()));
// Save after disconnecting
if (this.isLoggedIn()) {
Player player = getPlayer();
// Call logout event.
player.onLogout();
}
try {
send(new BasePacket(PacketOpcodes.ServerDisconnectClientNotify));
}catch (Throwable ignore){
Grasscutter.getLogger().warn("closing {} error",getAddress().getAddress().getHostAddress());
}
tunnel = null;
}
public void close() {
tunnel.close();
}
public boolean isActive() {
return getState() == SessionState.ACTIVE;
}
public enum SessionState { public enum SessionState {
INACTIVE, INACTIVE,
WAITING_FOR_TOKEN, WAITING_FOR_TOKEN,
WAITING_FOR_LOGIN, WAITING_FOR_LOGIN,
PICKING_CHARACTER, PICKING_CHARACTER,
ACTIVE; ACTIVE
} }
} }

View File

@ -0,0 +1,110 @@
package emu.grasscutter.server.game;
import java.net.InetSocketAddress;
import java.util.concurrent.ConcurrentHashMap;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.utils.Crypto;
import emu.grasscutter.utils.Utils;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.DefaultEventLoop;
import kcp.highway.KcpListener;
import kcp.highway.Ukcp;
public class GameSessionManager {
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) {
byte[] byteData = Utils.byteBufToArray(buf);
logicThread.execute(() -> {
try {
GameSession 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;
}
interface KcpTunnel{
InetSocketAddress getAddress();
void writeData(byte[] bytes);
void close();
int getSrtt();
}
interface KcpChannel{
void onConnected(KcpTunnel tunnel);
void handleClose();
void handleReceive(byte[] bytes);
}
}

View File

@ -8,6 +8,7 @@ import emu.grasscutter.net.packet.BasePacket;
import emu.grasscutter.net.packet.PacketOpcodes; import emu.grasscutter.net.packet.PacketOpcodes;
import emu.grasscutter.net.proto.DoGachaRspOuterClass.DoGachaRsp; import emu.grasscutter.net.proto.DoGachaRspOuterClass.DoGachaRsp;
import emu.grasscutter.net.proto.GachaItemOuterClass.GachaItem; import emu.grasscutter.net.proto.GachaItemOuterClass.GachaItem;
import emu.grasscutter.net.proto.RetcodeOuterClass.Retcode;
import emu.grasscutter.net.proto.RetcodeOuterClass; import emu.grasscutter.net.proto.RetcodeOuterClass;
public class PacketDoGachaRsp extends BasePacket { public class PacketDoGachaRsp extends BasePacket {
@ -42,4 +43,14 @@ public class PacketDoGachaRsp extends BasePacket {
this.setData(p); this.setData(p);
} }
public PacketDoGachaRsp(Retcode retcode) {
super(PacketOpcodes.DoGachaRsp);
DoGachaRsp p = DoGachaRsp.newBuilder()
.setRetcode(retcode.getNumber())
.build();
this.setData(p);
}
} }

View File

@ -4,6 +4,7 @@ import emu.grasscutter.game.entity.GameEntity;
import emu.grasscutter.game.props.FightProperty; import emu.grasscutter.game.props.FightProperty;
import emu.grasscutter.net.packet.BasePacket; import emu.grasscutter.net.packet.BasePacket;
import emu.grasscutter.net.packet.PacketOpcodes; import emu.grasscutter.net.packet.PacketOpcodes;
import emu.grasscutter.net.proto.ChangeEnergyReasonOuterClass.ChangeEnergyReason;
import emu.grasscutter.net.proto.ChangeHpReasonOuterClass.ChangeHpReason; import emu.grasscutter.net.proto.ChangeHpReasonOuterClass.ChangeHpReason;
import emu.grasscutter.net.proto.EntityFightPropChangeReasonNotifyOuterClass.EntityFightPropChangeReasonNotify; import emu.grasscutter.net.proto.EntityFightPropChangeReasonNotifyOuterClass.EntityFightPropChangeReasonNotify;
import emu.grasscutter.net.proto.PropChangeReasonOuterClass.PropChangeReason; import emu.grasscutter.net.proto.PropChangeReasonOuterClass.PropChangeReason;
@ -55,4 +56,17 @@ public class PacketEntityFightPropChangeReasonNotify extends BasePacket {
this.setData(proto); this.setData(proto);
} }
public PacketEntityFightPropChangeReasonNotify(GameEntity entity, FightProperty prop, Float value, ChangeEnergyReason reason) {
super(PacketOpcodes.EntityFightPropChangeReasonNotify);
EntityFightPropChangeReasonNotify proto = EntityFightPropChangeReasonNotify.newBuilder()
.setEntityId(entity.getId())
.setPropType(prop.getId())
.setPropDelta(value)
.setChangeEnergyReson(reason)
.build();
this.setData(proto);
}
} }

File diff suppressed because one or more lines are too long