Skip to content

Creating your own server and more

Vrekt edited this page May 11, 2023 · 1 revision

Want a more detailed example? Check out my server in my game

Servers have worlds just like the game.

It starts with the interface World which has methods for many common methods that you would use. ServerWorld implements World which just handles collections of entities and players. Then, AbstractServerWorld implements ServerWorld.

AbstractServerWorld

AbstractServerWorld requires two types, an instance of a server player and an instance of a server entity. It could either be the default implementation LunarServerEntityPlayer, LunarServerEntity or your own types that extend those classes.

Here is a small example of a custom world

public class CrimsonServerWorld extends AbstractServerWorld<CrimsonPlayer, CrimsonEntity> {

    public CrimsonServerWorld(ServerWorldConfiguration configuration, String worldName) {
        super(configuration, worldName);
    }

}

With the game server we can then add this world

worldManager = new CrimsonWorldManager();
gameServer.setWorldManager(worldManager);
gameServer.getWorldManager().addWorld("TutorialWorld", new CrimsonServerWorld(new ServerWorldConfiguration(), "TutorialWorld"));

Extending ServerPlayerConnection allows us to implement alot of game logic that we need, here is an example from my game

public final class CrimsonPlayerConnection extends ServerPlayerConnection {

    private CrimsonWorld worldIn;
    private CrimsonPlayer localPlayer;

    public CrimsonPlayerConnection(Channel channel, LunarServer server) {
        super(channel, server);

        registerPacket(ClientSpawnEntity.ID, ClientSpawnEntity::new, this::handleSpawnEntity);
        registerPacket(ClientEquipItem.ID, ClientEquipItem::new, this::handleEquipItem);
        registerPacket(ClientSwingItem.ID, ClientSwingItem::new, this::handleSwingItem);
    }

    @Override
    public void handleAuthentication(CPacketAuthentication packet) {
        Logging.info(this, "Attempting to authenticate a new player from [" + channel.localAddress() + "]");
        super.handleAuthentication(packet);
    }

    @Override
    public void handleJoinWorld(CPacketJoinWorld packet) {
        Logging.info(this, "New player requesting to join world: " + packet.getWorldName() + " with username " + packet.getUsername());

        if (packet.getUsername() == null || packet.getUsername().isEmpty()) {
            this.sendImmediately(new SPacketWorldInvalid(packet.getWorldName(), "Invalid username."));
            return;
        } else if ((packet.getWorldName() == null || packet.getWorldName().isEmpty()) || !server.getWorldManager().worldExists(packet.getWorldName())) {
            this.sendImmediately(new SPacketWorldInvalid(packet.getWorldName(), "World does not exist."));
            return;
        }

        final World world = server.getWorldManager().getWorld(packet.getWorldName());
        if (world.isFull()) {
            this.sendImmediately(new SPacketWorldInvalid(packet.getWorldName(), "World is full."));
            return;
        }

        this.worldIn = (CrimsonWorld) world;
        this.localPlayer = new CrimsonPlayer(true, server, this);
        this.localPlayer.setEntityName(packet.getUsername());
        this.localPlayer.setWorldIn(world);
        this.localPlayer.setEntityId(world.assignEntityIdFor(true));
        this.player = localPlayer;
        sendImmediately(new SPacketJoinWorld(packet.getWorldName(), localPlayer.getEntityId()));
    }

    @Override
    public void handleWorldLoaded(CPacketWorldLoaded packet) {
        super.handleWorldLoaded(packet);
        worldIn.handlePlayerLoaded(localPlayer);
    }

    private void handleSpawnEntity(ClientSpawnEntity packet) {
        Logging.info(this, "Spawning a new entity by request from player, type=" + packet.getType() + ", pos=" + packet.getPosition());

        if (worldIn != null) {
            worldIn.spawnEntityInWorld(packet.getType(), packet.getPosition());

            final ServerSpawnEntity entity = new ServerSpawnEntity(worldIn.assignEntityIdFor(false), packet.getType(), packet.getPosition());
            sendImmediately(entity);
        }
    }

    private void handleEquipItem(ClientEquipItem packet) {
        worldIn.broadcastNowWithExclusion(packet.getEntityId(), new ServerPlayerEquippedItem(packet.getEntityId(), packet.getItemId()));
    }

    private void handleSwingItem(ClientSwingItem packet) {
        worldIn.broadcastNowWithExclusion(packet.getEntityId(), new ServerPlayerSwungItem(packet.getEntityId(), packet.getItemId()));
    }

}

To have each new connection use that class, simply:

server = new NettyServer(ip, port, protocol, gameServer);
server.setConnectionProvider(socketChannel -> new CrimsonPlayerConnection(socketChannel, gameServer));
server.bind();