Archived
0

Core as module

This commit is contained in:
2018-04-22 22:04:16 +03:00
parent d8d5ed2b46
commit fc5e860068
26 changed files with 24 additions and 24 deletions

19
core/build.gradle Normal file
View File

@@ -0,0 +1,19 @@
group 'mc'
version '1.0-SNAPSHOT'
apply plugin: 'application'
mainClassName = "mc.core.Main"
ext {
log4j_version = '2.5'
}
dependencies {
/* Logger */
runtime (group: 'org.apache.logging.log4j', name: 'log4j-core', version: log4j_version)
runtime (group: 'org.apache.logging.log4j', name: 'log4j-slf4j-impl', version: log4j_version)
/* Components */
compile (group: 'commons-io', name: 'commons-io', version: '2.6')
}

View File

@@ -0,0 +1,11 @@
/*
* DmitriyMX <dimon550@gmail.com>
* 2018-04-08
*/
package mc.core;
public interface Config {
int getMaxPlayers();
String getDescriptionServer();
byte[] getFaviconBase64();
}

View File

@@ -0,0 +1,128 @@
/*
* DmitriyMX <dimon550@gmail.com>
* 2018-04-21
*/
package mc.core;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.Calendar;
import java.util.Random;
@Slf4j
public class GameLoop extends Thread {
@Autowired
PlayerManager playerManager;
/* TPS */
private int tps;
private long pause;
@Setter
private boolean traceTPS = false;
private int lowTps;
/* Time */
private long gameTime;
private Runnable gameTimeUpdateFunc;
public GameLoop() {
super();
setTps(20);
setPercentWarnLowTps(5);
setStartGameTime(0);
}
public void setPercentWarnLowTps(int value) {
if (value > 50) {
log.warn("Percent warn low TPS can't be '{}'. Set 100", tps);
value = 100;
}
this.lowTps = tps - (int)(tps * (value / 100f));
}
public void setTps(int tps) {
if (tps > 1000) {
log.warn("TPS can't be '{}'. Set 1000", tps);
tps = 1000;
}
this.tps = tps;
this.pause = (1000 / tps);
}
public void setStartGameTime(long value) {
this.gameTime = value;
}
public void setTimeMode(String mode) {
if (mode.equals("0") || mode.equalsIgnoreCase("idle")) {
gameTimeUpdateFunc = () -> {};
} else if (mode.equalsIgnoreCase("realtime")) {
gameTimeUpdateFunc = () -> {
final long DIFF = 21600L;
final long HOUR24 = 86400L;
final long SYSTIME = System.currentTimeMillis();
Calendar calendar = Calendar.getInstance();
calendar.setTimeInMillis(SYSTIME);
calendar.set(Calendar.HOUR_OF_DAY, 0);
calendar.set(Calendar.MINUTE, 0);
calendar.set(Calendar.SECOND, 0);
calendar.set(Calendar.MILLISECOND, 0);
long time = (SYSTIME - calendar.getTimeInMillis())/1000;
if (time < DIFF) time += HOUR24;
gameTime = (long) ((time - DIFF) / 3.6);
};
} else {
if (!mode.equalsIgnoreCase("normal")) {
log.warn("Unknown time mode: {}. Set normal mode", mode);
}
gameTimeUpdateFunc = () -> {
gameTime++;
if (gameTime > 24000) gameTime = 0;
};
}
}
@Override
public void run() {
log.info("Target TPS: {}; Low TPS: {}", tps, lowTps);
int factTps = 0;
long lastTime = System.currentTimeMillis();
while (!isInterrupted()) {
if ((System.currentTimeMillis() - lastTime) > 1000) {
lastTime = System.currentTimeMillis();
if (factTps < lowTps) {
log.warn("Low TPS: {}/{}", factTps, tps);
} else if (traceTPS) {
log.info("TPS: {}/{}", factTps, tps);
}
factTps = 0;
}
long futureTime = System.currentTimeMillis() + pause;
/* --- --- --- */
gameTimeUpdateFunc.run();
/* --- --- --- */
playerManager.getBroadcastChannel().sendTimeUpdate(gameTime);
/* --- --- --- */
factTps++;
try {
long pause = futureTime - System.currentTimeMillis();
Thread.sleep((pause <= 0 ? 0 : pause));
} catch (InterruptedException ignored) {
}
}
}
}

View File

@@ -0,0 +1,14 @@
/*
* DmitriyMX <dimon550@gmail.com>
* 2018-04-15
*/
package mc.core;
import lombok.AllArgsConstructor;
import lombok.Data;
@Data
@AllArgsConstructor
public class Location {
private double x, y, z;
}

View File

@@ -0,0 +1,14 @@
/*
* DmitriyMX <dimon550@gmail.com>
* 2018-04-22
*/
package mc.core;
import lombok.AllArgsConstructor;
import lombok.Data;
@Data
@AllArgsConstructor
public class Look {
private float yaw, pitch;
}

View File

@@ -0,0 +1,30 @@
/*
* DmitriyMX <dimon550@gmail.com>
* 2018-03-25
*/
package mc.core;
import lombok.extern.slf4j.Slf4j;
import mc.core.network.Server;
import mc.core.network.StartServerException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
@Slf4j
public class Main {
public static void main(String[] args) {
ApplicationContext appContext = new ClassPathXmlApplicationContext("spring.xml");
GameLoop gameLoop = appContext.getBean(GameLoop.class);
gameLoop.start();
Server server = appContext.getBean("server", Server.class);
try {
server.start();
} catch (StartServerException e) {
log.error("Can't start server", e);
}
gameLoop.interrupt();
}
}

View File

@@ -0,0 +1,27 @@
/*
* DmitriyMX <dimon550@gmail.com>
* 2018-04-13
*/
package mc.core;
import mc.core.network.NetChannel;
public interface Player {
int getId();
void setId(int value);
String getName();
void setName(String value);
boolean isOnline();
void setOnline(boolean status);
NetChannel getChannel();
void setChannel(NetChannel channel);
Location getLocation();
void setLocation(Location location);
Look getLook();
void setLook(Look look);
}

View File

@@ -0,0 +1,17 @@
/*
* DmitriyMX <dimon550@gmail.com>
* 2018-04-15
*/
package mc.core;
import mc.core.network.NetChannel;
import java.util.Optional;
public interface PlayerManager {
void addPlayer(Player player);
Optional<Player> getPlayer(String name);
Optional<Player> getPlayerById(int id);
void removePlayer(Player player);
NetChannel getBroadcastChannel();
}

View File

@@ -0,0 +1,37 @@
/*
* DmitriyMX <dimon550@gmail.com>
* 2018-04-08
*/
package mc.core.embedded;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import mc.core.Config;
import org.apache.commons.io.FileUtils;
import java.io.File;
import java.io.IOException;
import java.util.Base64;
@Slf4j
@Getter
public class ConfigFromSpring implements Config {
@Setter
private String descriptionServer;
private byte[] faviconBase64;
@Setter
private int maxPlayers;
public void setFaviconBase64(File faviconImageFile) {
log.debug("faviconImageFile: {}", faviconImageFile.getAbsolutePath());
try {
faviconBase64 = Base64.getEncoder().encode(
FileUtils.readFileToByteArray(faviconImageFile)
);
} catch (IOException e) {
log.warn("Can't load favicon", e);
faviconBase64 = null;
}
}
}

View File

@@ -0,0 +1,96 @@
/*
* DmitriyMX <dimon550@gmail.com>
* 2018-04-15
*/
package mc.core.embedded;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import mc.core.Config;
import mc.core.Player;
import mc.core.PlayerManager;
import mc.core.network.BroadcastNetChannel;
import mc.core.network.NetChannel;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
@Slf4j
public class InMemoryPlayerManager implements PlayerManager, Runnable {
private List<Player> players;
private final Object lock = new Object();
@Setter
private int keepAliveInterval = 1;
@Autowired
public InMemoryPlayerManager(Config config) {
final int c = config.getMaxPlayers() > 50 ? 50 : config.getMaxPlayers();
players = Collections.synchronizedList(new ArrayList<>(c));
(new Thread(this, "KeepAlive")).start();
}
@Override
public void addPlayer(Player player) {
synchronized (lock) {
players.add(player);
lock.notify();
log.info("Player \"{}\" join server", player.getName());
}
}
@Override
public Optional<Player> getPlayer(final String name) {
return players.stream()
.filter(player -> player.getName().equalsIgnoreCase(name))
.findFirst();
}
@Override
public Optional<Player> getPlayerById(final int id) {
return players.stream()
.filter(player -> player.getId() == id)
.findFirst();
}
@Override
public void removePlayer(Player player) {
player.setOnline(false);
player.setChannel(null);
log.info("Player \"{}\" left server", player.getName());
}
@Override
public NetChannel getBroadcastChannel() {
return new BroadcastNetChannel(
players.stream()
.filter(Player::isOnline)
.filter(player -> player.getChannel() != null)
);
}
@Override
public void run() {
while (!Thread.currentThread().isInterrupted()) {
synchronized (lock) {
while (players.size() == 0) {
try {
lock.wait();
} catch (InterruptedException e) {
return;
}
}
}
getBroadcastChannel().sendKeepAlive();
try {
Thread.sleep(keepAliveInterval);
} catch (InterruptedException e) {
return;
}
}
}
}

View File

@@ -0,0 +1,30 @@
/*
* DmitriyMX <dimon550@gmail.com>
* 2018-04-21
*/
package mc.core.network;
import lombok.RequiredArgsConstructor;
import mc.core.Player;
import java.util.stream.Stream;
@RequiredArgsConstructor
public class BroadcastNetChannel implements NetChannel {
private final Stream<Player> playerStream;
@Override
public void sendKeepAlive() {
playerStream.forEach(player -> player.getChannel().sendKeepAlive());
}
@Override
public void sendTimeUpdate(final long value) {
playerStream.forEach(player -> player.getChannel().sendTimeUpdate(value));
}
@Override
public void writeAndFlush(final SCPacket pkt) {
playerStream.forEach(player -> player.getChannel().writeAndFlush(pkt));
}
}

View File

@@ -0,0 +1,9 @@
/*
* DmitriyMX <dimon550@gmail.com>
* 2018-04-08
*/
package mc.core.network;
public interface CSPacket {
void readSelf(NetStream netStream);
}

View File

@@ -0,0 +1,12 @@
/*
* DmitriyMX <dimon550@gmail.com>
* 2018-04-13
*/
package mc.core.network;
public interface NetChannel {
void sendKeepAlive();
void sendTimeUpdate(long value);
void writeAndFlush(SCPacket pkt);
}

View File

@@ -0,0 +1,37 @@
/*
* DmitriyMX <dimon550@gmail.com>
* 2018-03-28
*/
package mc.core.network;
import lombok.Getter;
import lombok.Setter;
public abstract class NetStream {
@Getter
@Setter
private int expectedSize;
public abstract boolean readBoolean();
public abstract byte readByte();
public abstract void readBytes(byte[] buffer);
public abstract int readUnsignedByte();
public abstract int readUnsignedShort();
public abstract short readShort();
public abstract int readInt();
public abstract float readFloat();
public abstract double readDouble();
public abstract String readString();
public abstract void writeBoolean(boolean value);
public abstract void writeByte(int value);
public abstract void writeBytes(byte[] buffer);
public abstract void writeShort(int value);
public abstract void writeInt(int value);
public abstract void writeLong(long value);
public abstract void writeFloat(float value);
public abstract void writeDouble(double value);
public abstract void writeString(String value);
public abstract void skipBytes(int count);
}

View File

@@ -0,0 +1,9 @@
/*
* DmitriyMX <dimon550@gmail.com>
* 2018-04-08
*/
package mc.core.network;
public interface SCPacket {
byte[] toByteArray();
}

View File

@@ -0,0 +1,9 @@
/*
* DmitriyMX <dimon550@gmail.com>
* 2018-03-25
*/
package mc.core.network;
public interface Server {
void start() throws StartServerException;
}

View File

@@ -0,0 +1,11 @@
/*
* DmitriyMX <dimon550@gmail.com>
* 2018-03-25
*/
package mc.core.network;
public class StartServerException extends Exception {
public StartServerException(Throwable cause) {
super(cause);
}
}

View File

@@ -0,0 +1,21 @@
/*
* DmitriyMX <dimon550@gmail.com>
* 2018-04-15
*/
package mc.core.world;
import mc.core.Location;
public interface Block {
Location getLocation();
void setLocation(Location location);
int getType();
void setType(int value);
int getMetadata();
void setMetadata(int value);
int getLight();
void setLight(int value);
}

View File

@@ -0,0 +1,23 @@
/*
* DmitriyMX <dimon550@gmail.com>
* 2018-04-15
*/
package mc.core.world;
/* 16x256x16 */
public interface Chunk {
Block getBlock(int x, int y, int z);
void setBlock(Block block);
int getBlockType(int x, int y, int z);
void setBlockType(int x, int y, int z, int type);
int getBlockMetadata(int x, int y, int z);
void setBlockMetadata(int x, int y, int z, int metadata);
int getBlockLight(int x, int y, int z);
void setBlockLight(int x, int y, int z, int lightLevel);
int getSkyLight(int x, int y, int z);
void setSkyLight(int x, int y, int z, int lightLevel);
}

View File

@@ -0,0 +1,15 @@
/*
* DmitriyMX <dimon550@gmail.com>
* 2018-04-15
*/
package mc.core.world;
import mc.core.Location;
public interface World {
Location getSpawn();
void setSpawn(Location location);
Chunk getChunk(int x, int z);
void setChunk(int x, int z, Chunk chunk);
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<Configuration>
<Appenders>
<Console name="Console" target="SYSTEM_OUT">
<PatternLayout pattern="%d{HH:mm:ss.SSS} [%-5level] \{%20.20c\:%-4L} %msg%n"/>
</Console>
</Appenders>
<Loggers>
<Root level="trace">
<AppenderRef ref="Console"/>
</Root>
</Loggers>
</Configuration>

View File

@@ -0,0 +1,34 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<context:annotation-config />
<bean id="config" class="mc.core.embedded.ConfigFromSpring">
<property name="descriptionServer" value="MC Core"/>
<property name="maxPlayers" value="100"/>
<property name="faviconBase64" value="icon.png"/>
</bean>
<bean id="gameLoop" class="mc.core.GameLoop">
<property name="startGameTime" value="6000"/>
<property name="timeMode" value="realtime"/>
</bean>
<bean id="playerManager" class="mc.core.embedded.InMemoryPlayerManager">
<property name="keepAliveInterval" value="10"/>
</bean>
<!-- Netty Server -->
<bean id="pipeline.log" class="io.netty.handler.logging.LoggingHandler" scope="prototype"/>
<bean id="pipeline.decoder" class="mc.core.network.proto_125.netty.PacketDecoder" scope="prototype"/>
<bean id="pipeline.encoder" class="mc.core.network.proto_125.netty.PacketEncoder" scope="prototype"/>
<bean id="pipeline.handler" class="mc.core.network.proto_125.netty.PacketHandler" scope="prototype"/>
<bean id="server" class="mc.core.network.proto_125.netty.NettyServer">
<property name="host" value="127.0.0.1"/>
<property name="port" value="25565"/>
<property name="workerGroupCount" value="2"/>
</bean>
</beans>