diff --git a/core/build.gradle b/core/build.gradle index fce3364..bad551b 100644 --- a/core/build.gradle +++ b/core/build.gradle @@ -12,4 +12,5 @@ ext { dependencies { /* Components */ compile (group: 'commons-io', name: 'commons-io', version: '2.6') + compile (group: 'com.google.guava', name: 'guava', version: '24.1-jre') } diff --git a/core/src/main/java/mc/core/PlayerManager.java b/core/src/main/java/mc/core/PlayerManager.java index 0edfd42..34d360f 100644 --- a/core/src/main/java/mc/core/PlayerManager.java +++ b/core/src/main/java/mc/core/PlayerManager.java @@ -15,5 +15,6 @@ public interface PlayerManager { void leftServer(Player player); Optional getPlayer(String name); List getPlayers(); + int getCountOnlinePlayers(); NetChannel getBroadcastChannel(); } diff --git a/core/src/main/java/mc/core/chat/ChatProcessor.java b/core/src/main/java/mc/core/chat/ChatProcessor.java new file mode 100644 index 0000000..5219781 --- /dev/null +++ b/core/src/main/java/mc/core/chat/ChatProcessor.java @@ -0,0 +1,15 @@ +/* + * DmitriyMX + * 2018-05-06 + */ +package mc.core.chat; + +import mc.core.Player; +import org.slf4j.Marker; +import org.slf4j.helpers.BasicMarkerFactory; + +public abstract class ChatProcessor { + protected static final Marker CHAT_MARKER = new BasicMarkerFactory().getMarker("Chat"); + + public abstract void process(Player player, String message); +} diff --git a/core/src/main/java/mc/core/ChatStyle.java b/core/src/main/java/mc/core/chat/ChatStyle.java similarity index 84% rename from core/src/main/java/mc/core/ChatStyle.java rename to core/src/main/java/mc/core/chat/ChatStyle.java index 9483244..9621120 100644 --- a/core/src/main/java/mc/core/ChatStyle.java +++ b/core/src/main/java/mc/core/chat/ChatStyle.java @@ -2,7 +2,7 @@ * DmitriyMX * 2018-04-30 */ -package mc.core; +package mc.core.chat; import java.util.regex.Pattern; @@ -24,15 +24,15 @@ public enum ChatStyle { YELLOW('e'), WHITE ('f'); - private static final char COLOR_CHAR = '\u00a7'; // § + public static final char SPECIAL_CHAR = '\u00a7'; // § private static final String codes = "0123456789aAbBcCdDeEfF"; - private static final Pattern EXCAPE_PATTERN = Pattern.compile(COLOR_CHAR + "[0-9a-f]", Pattern.CASE_INSENSITIVE); + private static final Pattern EXCAPE_PATTERN = Pattern.compile(SPECIAL_CHAR + "[0-9a-f]", Pattern.CASE_INSENSITIVE); public static String format(char colorChar, String message) { char[] chars = message.toCharArray(); for (int i = 0; i < chars.length; i++) { if (chars[i] == colorChar && codes.indexOf(chars[i+1]) > -1) { - chars[i] = COLOR_CHAR; + chars[i] = SPECIAL_CHAR; chars[i+1] = Character.toLowerCase(chars[i+1]); i++; } @@ -48,7 +48,7 @@ public enum ChatStyle { private char[] toString; ChatStyle(char ch) { - toString = new char[]{ COLOR_CHAR, ch }; + toString = new char[]{SPECIAL_CHAR, ch }; } @Override diff --git a/core/src/main/java/mc/core/chat/SimpleChatProcessor.java b/core/src/main/java/mc/core/chat/SimpleChatProcessor.java new file mode 100644 index 0000000..40f3edc --- /dev/null +++ b/core/src/main/java/mc/core/chat/SimpleChatProcessor.java @@ -0,0 +1,26 @@ +/* + * DmitriyMX + * 2018-05-06 + */ +package mc.core.chat; + +import lombok.extern.slf4j.Slf4j; +import mc.core.Player; +import mc.core.PlayerManager; +import org.springframework.beans.factory.annotation.Autowired; + +@Slf4j +public class SimpleChatProcessor extends ChatProcessor { + @Autowired + private PlayerManager playerManager; + + @Override + public void process(Player player, String message) { + log.info(CHAT_MARKER, "<{}> {}", player.getName(), ChatStyle.escapeStyle(message)); + playerManager.getBroadcastChannel().sendChatMessage( + ChatStyle.GOLD + player.getName() + + ChatStyle.GRAY + ": " + + ChatStyle.WHITE + message + ); + } +} diff --git a/core/src/main/java/mc/core/embedded/InMemoryPlayerManager.java b/core/src/main/java/mc/core/embedded/InMemoryPlayerManager.java index 5e37129..915a1ae 100644 --- a/core/src/main/java/mc/core/embedded/InMemoryPlayerManager.java +++ b/core/src/main/java/mc/core/embedded/InMemoryPlayerManager.java @@ -66,6 +66,11 @@ public class InMemoryPlayerManager implements PlayerManager, Runnable { return players.stream().filter(Player::isOnline).collect(Collectors.toList()); } + @Override + public int getCountOnlinePlayers() { + return players.size(); + } + @Override public NetChannel getBroadcastChannel() { return new BroadcastNetChannel(players.stream().filter(Player::isOnline)); diff --git a/core/src/main/java/mc/core/events/Event.java b/core/src/main/java/mc/core/events/Event.java new file mode 100644 index 0000000..066e7c8 --- /dev/null +++ b/core/src/main/java/mc/core/events/Event.java @@ -0,0 +1,13 @@ +/* + * DmitriyMX + * 2018-05-02 + */ +package mc.core.events; + +public interface Event { + void setCanceled(boolean value); + boolean isCanceled(); + + void setLastProcess(boolean value); + boolean isLastProcess(); +} diff --git a/core/src/main/java/mc/core/events/EventBase.java b/core/src/main/java/mc/core/events/EventBase.java new file mode 100644 index 0000000..d633d71 --- /dev/null +++ b/core/src/main/java/mc/core/events/EventBase.java @@ -0,0 +1,17 @@ +/* + * DmitriyMX + * 2018-05-02 + */ +package mc.core.events; + +import lombok.Getter; +import lombok.Setter; + +public abstract class EventBase implements Event { + @Getter + @Setter + private boolean canceled; + @Getter + @Setter + private boolean lastProcess; +} diff --git a/core/src/main/java/mc/core/events/EventBusGetter.java b/core/src/main/java/mc/core/events/EventBusGetter.java new file mode 100644 index 0000000..d2e5aa3 --- /dev/null +++ b/core/src/main/java/mc/core/events/EventBusGetter.java @@ -0,0 +1,14 @@ +/* + * DmitriyMX + * 2018-05-02 + */ +package mc.core.events; + +import com.google.common.eventbus.EventBus; + +public final class EventBusGetter { + public static final EventBus INSTANCE = new EventBus(); + + private EventBusGetter() { + } +} diff --git a/core/src/main/java/mc/core/events/LoginEvent.java b/core/src/main/java/mc/core/events/LoginEvent.java new file mode 100644 index 0000000..63e123f --- /dev/null +++ b/core/src/main/java/mc/core/events/LoginEvent.java @@ -0,0 +1,21 @@ +/* + * DmitriyMX + * 2018-05-02 + */ +package mc.core.events; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import lombok.Setter; + +import java.net.SocketAddress; + +@RequiredArgsConstructor +@Getter +@Setter +public class LoginEvent extends EventBase { + private String playerName; + private final SocketAddress remoteAddress; + private boolean deny; + private String denyReason; +} diff --git a/core/src/main/java/mc/core/events/PlayerLookEvent.java b/core/src/main/java/mc/core/events/PlayerLookEvent.java new file mode 100644 index 0000000..c478e95 --- /dev/null +++ b/core/src/main/java/mc/core/events/PlayerLookEvent.java @@ -0,0 +1,19 @@ +/* + * DmitriyMX + * 2018-05-02 + */ +package mc.core.events; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import lombok.Setter; +import mc.core.Look; +import mc.core.Player; + +@RequiredArgsConstructor +@Getter +@Setter +public class PlayerLookEvent extends EventBase { + private final Player player; + private Look newLook; +} diff --git a/core/src/main/java/mc/core/events/PlayerPositionEvent.java b/core/src/main/java/mc/core/events/PlayerPositionEvent.java new file mode 100644 index 0000000..fc36919 --- /dev/null +++ b/core/src/main/java/mc/core/events/PlayerPositionEvent.java @@ -0,0 +1,19 @@ +/* + * DmitriyMX + * 2018-05-02 + */ +package mc.core.events; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import lombok.Setter; +import mc.core.Location; +import mc.core.Player; + +@RequiredArgsConstructor +@Getter +@Setter +public class PlayerPositionEvent extends EventBase { + private final Player player; + private Location newPosition; +} diff --git a/core/src/main/java/mc/core/events/ServerPingEvent.java b/core/src/main/java/mc/core/events/ServerPingEvent.java new file mode 100644 index 0000000..3bbafce --- /dev/null +++ b/core/src/main/java/mc/core/events/ServerPingEvent.java @@ -0,0 +1,21 @@ +/* + * DmitriyMX + * 2018-05-02 + */ +package mc.core.events; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import lombok.Setter; + +import java.net.SocketAddress; + +@RequiredArgsConstructor +@Getter +@Setter +public class ServerPingEvent extends EventBase { + private final SocketAddress remoteAddress; + private String description; + private int online; + private int maxOnline; +} diff --git a/proto125/src/main/java/mc/core/network/proto_125/packets/KickPacket.java b/proto125/src/main/java/mc/core/network/proto_125/packets/KickPacket.java index c5c49d6..10e080e 100644 --- a/proto125/src/main/java/mc/core/network/proto_125/packets/KickPacket.java +++ b/proto125/src/main/java/mc/core/network/proto_125/packets/KickPacket.java @@ -22,10 +22,6 @@ import mc.core.network.proto_125.ByteArrayOutputNetStream; public class KickPacket implements SCPacket, CSPacket { private String reason; - public void setPongMessage(String description, int online, int maxOnline) { - reason = String.format("%s§%d§%d", description, online, maxOnline); - } - public String getReason() { return (reason == null ? "" : reason); } diff --git a/proto125_netty/src/main/java/mc/core/network/proto_125/netty/EventListener.java b/proto125_netty/src/main/java/mc/core/network/proto_125/netty/EventListener.java new file mode 100644 index 0000000..bf0f615 --- /dev/null +++ b/proto125_netty/src/main/java/mc/core/network/proto_125/netty/EventListener.java @@ -0,0 +1,42 @@ +/* + * DmitriyMX + * 2018-05-02 + */ +package mc.core.network.proto_125.netty; + +import com.google.common.eventbus.Subscribe; +import lombok.RequiredArgsConstructor; +import mc.core.Config; +import mc.core.Player; +import mc.core.PlayerManager; +import mc.core.events.LoginEvent; +import mc.core.events.ServerPingEvent; + +import java.util.Optional; + +@RequiredArgsConstructor +public class EventListener { + private final Config config; + private final PlayerManager playerManager; + + @Subscribe + public void onServerPingEvent(ServerPingEvent event) { + if (event.isLastProcess() || event.isCanceled()) return; + + event.setDescription(config.getDescriptionServer()); + event.setOnline(playerManager.getCountOnlinePlayers()); + event.setMaxOnline(config.getMaxPlayers()); + } + + @Subscribe + public void onLoginEvent(LoginEvent event) { + if (event.isLastProcess()) return; + + Optional optPlayer = playerManager.getPlayer(event.getPlayerName()); + + if (optPlayer.isPresent() && optPlayer.get().isOnline()) { + event.setDeny(true); + event.setDenyReason("Player is exists in server"); + } + } +} diff --git a/proto125_netty/src/main/java/mc/core/network/proto_125/netty/NettyServer.java b/proto125_netty/src/main/java/mc/core/network/proto_125/netty/NettyServer.java index 9a9c93b..9add56e 100644 --- a/proto125_netty/src/main/java/mc/core/network/proto_125/netty/NettyServer.java +++ b/proto125_netty/src/main/java/mc/core/network/proto_125/netty/NettyServer.java @@ -13,11 +13,16 @@ import io.netty.channel.socket.SocketChannel; import io.netty.channel.socket.nio.NioServerSocketChannel; import lombok.Setter; import lombok.extern.slf4j.Slf4j; +import mc.core.Config; +import mc.core.PlayerManager; +import mc.core.events.EventBusGetter; import mc.core.network.Server; import mc.core.network.StartServerException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationContext; +import javax.annotation.PostConstruct; +import javax.annotation.PreDestroy; import java.util.Map; @Slf4j @@ -31,6 +36,20 @@ public class NettyServer implements Server { @Setter private int workerGroupCount = 0; private EventLoopGroup bossGroup, workerGroup; + private EventListener eventListener; + + @PostConstruct + public void init() { + eventListener = new EventListener( + applicationContext.getBean(Config.class), + applicationContext.getBean(PlayerManager.class)); + EventBusGetter.INSTANCE.register(eventListener); + } + + @PreDestroy + public void destruct() { + EventBusGetter.INSTANCE.unregister(eventListener); + } private ChannelInitializer buildChannelInitializer() { return new ChannelInitializer() { diff --git a/proto125_netty/src/main/java/mc/core/network/proto_125/netty/PacketHandler.java b/proto125_netty/src/main/java/mc/core/network/proto_125/netty/PacketHandler.java index cce6f47..fa241fe 100644 --- a/proto125_netty/src/main/java/mc/core/network/proto_125/netty/PacketHandler.java +++ b/proto125_netty/src/main/java/mc/core/network/proto_125/netty/PacketHandler.java @@ -12,12 +12,13 @@ import io.netty.channel.SimpleChannelInboundHandler; import io.netty.util.AttributeKey; import lombok.extern.slf4j.Slf4j; import mc.core.*; +import mc.core.chat.ChatProcessor; +import mc.core.chat.ChatStyle; +import mc.core.events.*; import mc.core.network.CSPacket; import mc.core.network.proto_125.netty.wrappers.WrapperNetChannel; import mc.core.network.proto_125.packets.*; import mc.core.world.World; -import org.slf4j.Marker; -import org.slf4j.helpers.BasicMarkerFactory; import org.springframework.beans.factory.annotation.Autowired; import java.lang.reflect.Method; @@ -27,13 +28,14 @@ import java.util.Optional; @Slf4j public class PacketHandler extends SimpleChannelInboundHandler { private static final AttributeKey ATTR_PLAYER = AttributeKey.newInstance("ATTR_PLAYER"); - private static final Marker CHAT_MARKER = new BasicMarkerFactory().getMarker("Chat"); @Autowired private Config config; @Autowired private PlayerManager playerManager; @Autowired private World world; + @Autowired + private ChatProcessor chatProcessor; @Override public void channelInactive(ChannelHandlerContext context) throws Exception { @@ -61,122 +63,148 @@ public class PacketHandler extends SimpleChannelInboundHandler { } } - public void onPingPacket(Channel channel, PingPacket packet) { - KickPacket pkt = new KickPacket(); - pkt.setPongMessage(config.getDescriptionServer(), 0, config.getMaxPlayers()); - channel.writeAndFlush(pkt); + private void onPingPacket(Channel channel, PingPacket packet) { + ServerPingEvent event = new ServerPingEvent(channel.remoteAddress()); + EventBusGetter.INSTANCE.post(event); + + if (event.isCanceled()) { + channel.disconnect(); + } else { + String response = String.format("%s%s%d%s%d", + event.getDescription(), ChatStyle.SPECIAL_CHAR, + event.getOnline(), ChatStyle.SPECIAL_CHAR, + event.getMaxOnline() + ); + + KickPacket pkt = new KickPacket(); + pkt.setReason(response); + channel.writeAndFlush(pkt); + } } - public void onHandshakePacket(Channel channel, HandshakePacket packet) { + private void onHandshakePacket(Channel channel, HandshakePacket packet) { channel.writeAndFlush(packet); } - public void onLoginPacket(Channel channel, LoginPacket packet) { - Player player; + private void onLoginPacket(Channel channel, LoginPacket packet) { + LoginEvent event = new LoginEvent(channel.remoteAddress()); + event.setPlayerName(packet.getPlayerName()); + EventBusGetter.INSTANCE.post(event); - Optional optPlayer = playerManager.getPlayer(packet.getPlayerName()); - if (optPlayer.isPresent()) { - player = optPlayer.get(); - - if (player.isOnline()) { - channel.writeAndFlush(new KickPacket("Player is exists in server")) - .addListener(ChannelFutureListener.CLOSE); - return; - } + if (event.isDeny()) { + channel.writeAndFlush(new KickPacket(event.getDenyReason())) + .addListener(ChannelFutureListener.CLOSE); } else { - player = playerManager.createPlayer(packet.getPlayerName()); + Player player = playerManager.createPlayer(packet.getPlayerName()); player.setLocation(world.getSpawn()); player.setLook(new Look(0f, 0f)); + + // Response login + packet.setPlayerId(player.getId()); + packet.setLevelType("flat"); + packet.setServerMode(1/*creative*/); + packet.setDimension(0/*Overworld*/); + packet.setDifficulty(0/*Peaceful*/); + packet.setMaxPlayers(config.getMaxPlayers()); + channel.write(packet); + + // send Spawn position + SpawnPositionPacket spawnPkt = new SpawnPositionPacket(); + spawnPkt.setLocation(world.getSpawn()); + channel.write(spawnPkt); + + // send Player abilities + PlayerAbilitiesPacket abilitiesPkt = new PlayerAbilitiesPacket(); + abilitiesPkt.setCanFly(true); + abilitiesPkt.setFlying(true); + abilitiesPkt.setGodMode(true); + abilitiesPkt.setInstantDestroyBlocks(true); + channel.write(abilitiesPkt); + + // send Chunk allocation + ChunkAllocationPacket chInitPkt = new ChunkAllocationPacket(); + chInitPkt.setX(0); + chInitPkt.setZ(0); + chInitPkt.setInitChunk(true); + channel.write(chInitPkt); + + // send Chunk data + ChunkDataPacket chDataPkt = new ChunkDataPacket(); + chDataPkt.setX(0); + chDataPkt.setZ(0); + chDataPkt.setChunk(world.getChunk(0, 0)); + chDataPkt.setNeedInitChunk(true); + chDataPkt.setYMin(1); + chDataPkt.setYMax(0); + channel.write(chDataPkt); + + // send Position and look + PositionAndLookPacket posLookPkt = new PositionAndLookPacket(); + posLookPkt.setLocation(player.getLocation()); + posLookPkt.setStance(player.getLocation().getY() + 1.64d); + posLookPkt.setLook(player.getLook()); + posLookPkt.setOnGround(false); + channel.write(posLookPkt); + channel.flush(); + + // send Spawn named entity + SpawnNamedEntityPacket spawnPlayer = new SpawnNamedEntityPacket(); + spawnPlayer.setId(player.getId()); + spawnPlayer.setEntityName(player.getName()); + spawnPlayer.setPosition(player.getLocation()); + spawnPlayer.setLook(player.getLook()); + playerManager.getBroadcastChannel().writeAndFlush(spawnPlayer); + + channel.attr(ATTR_PLAYER).set(player); + player.setChannel(new WrapperNetChannel(channel)); + playerManager.joinServer(player); } - - // Response login - packet.setPlayerId(player.getId()); - packet.setLevelType("flat"); - packet.setServerMode(1/*creative*/); - packet.setDimension(0/*Overworld*/); - packet.setDifficulty(0/*Peaceful*/); - packet.setMaxPlayers(config.getMaxPlayers()); - channel.write(packet); - - // send Spawn position - SpawnPositionPacket spawnPkt = new SpawnPositionPacket(); - spawnPkt.setLocation(world.getSpawn()); - channel.write(spawnPkt); - - // send Player abilities - PlayerAbilitiesPacket abilitiesPkt = new PlayerAbilitiesPacket(); - abilitiesPkt.setCanFly(true); - abilitiesPkt.setFlying(true); - abilitiesPkt.setGodMode(true); - abilitiesPkt.setInstantDestroyBlocks(true); - channel.write(abilitiesPkt); - - // send Chunk allocation - ChunkAllocationPacket chInitPkt = new ChunkAllocationPacket(); - chInitPkt.setX(0); - chInitPkt.setZ(0); - chInitPkt.setInitChunk(true); - channel.write(chInitPkt); - - // send Chunk data - ChunkDataPacket chDataPkt = new ChunkDataPacket(); - chDataPkt.setX(0); - chDataPkt.setZ(0); - chDataPkt.setChunk(world.getChunk(0, 0)); - chDataPkt.setNeedInitChunk(true); - chDataPkt.setYMin(1); - chDataPkt.setYMax(0); - channel.write(chDataPkt); - - // send Position and look - PositionAndLookPacket posLookPkt = new PositionAndLookPacket(); - posLookPkt.setLocation(player.getLocation()); - posLookPkt.setStance(player.getLocation().getY() + 1.64d); - posLookPkt.setLook(player.getLook()); - posLookPkt.setOnGround(false); - channel.write(posLookPkt); - channel.flush(); - - // send Spawn named entity - SpawnNamedEntityPacket spawnPlayer = new SpawnNamedEntityPacket(); - spawnPlayer.setId(player.getId()); - spawnPlayer.setEntityName(player.getName()); - spawnPlayer.setPosition(player.getLocation()); - spawnPlayer.setLook(player.getLook()); - playerManager.getBroadcastChannel().writeAndFlush(spawnPlayer); - - channel.attr(ATTR_PLAYER).set(player); - player.setChannel(new WrapperNetChannel(channel)); - playerManager.joinServer(player); } - public void onKickPacket(Channel channel, KickPacket packet) { + private void onKickPacket(Channel channel, KickPacket packet) { if (packet.getReason().equals("Quitting")) { channel.disconnect(); } } - public void onPlayerPositionPacket(Channel channel, PlayerPositionPacket packet) { + private void onPlayerPositionPacket(Channel channel, PlayerPositionPacket packet) { Player player = channel.attr(ATTR_PLAYER).get(); - player.getLocation().setX(packet.getX()); - player.getLocation().setY(packet.getY()); - player.getLocation().setZ(packet.getZ()); + PlayerPositionEvent event = new PlayerPositionEvent(player); + event.setNewPosition(new Location(packet.getX(), packet.getY(), packet.getZ())); + EventBusGetter.INSTANCE.post(event); + + if (!event.isCanceled()) { + player.getLocation().setX(event.getNewPosition().getX()); + player.getLocation().setY(event.getNewPosition().getY()); + player.getLocation().setZ(event.getNewPosition().getZ()); + + //TODO если позиция была изменена, нужно оповестить клиент + } } - public void onPlayerLookPacket(Channel channel, PlayerLookPacket packet) { + private void onPlayerLookPacket(Channel channel, PlayerLookPacket packet) { Player player = channel.attr(ATTR_PLAYER).get(); - player.getLook().setYaw(packet.getYaw()); - player.getLook().setPitch(packet.getPitch()); + PlayerLookEvent event = new PlayerLookEvent(player); + event.setNewLook(new Look(packet.getYaw(), packet.getPitch())); + EventBusGetter.INSTANCE.post(event); + + if (!event.isCanceled()) { + player.getLook().setYaw(event.getNewLook().getYaw()); + player.getLook().setPitch(event.getNewLook().getPitch()); + + //TODO если обзор был изменен, нужно оповестить клиент + } } - public void onPlayerAbilitiesPacket(Channel channel, PlayerAbilitiesPacket packet) { - log.debug("Player new sets: {}", packet.toString()); + private void onPlayerAbilitiesPacket(Channel channel, PlayerAbilitiesPacket packet) { Player player = channel.attr(ATTR_PLAYER).get(); player.setFlying(packet.isFlying()); } - public void onChatMessagePacket(Channel channel, ChatMessagePacket packet) { - log.info(CHAT_MARKER, "<{}>: {}", channel.attr(ATTR_PLAYER).get().getName(), ChatStyle.escapeStyle(packet.getMessage())); - playerManager.getBroadcastChannel().writeAndFlush(packet); + private void onChatMessagePacket(Channel channel, ChatMessagePacket packet) { + chatProcessor.process( + channel.attr(ATTR_PLAYER).get(), + packet.getMessage() + ); } }