Join our Discord server!
OpenSectors allows you to split your world into different servers using Kryonet and other networking solutions. This plugin splits your world into few pieces and each piece is different server. Everything is properly synchronized so it feels like it is one server. It allows you to have more players playing on one world, because it is actually working on few servers, so nothing will stop you from having 3000+ players on a single world!
It also supports basic authentication systems like checking password during the sector connection. That way people who want to connect using their own software to your server and do something bad to it, can't do it!
This presentation will be quite long, so here you have quick links to everything.
- Features
- Future of the project
- How it works?
- Do I need redis?
- How do I use it?
- Configuration
- API
- Contribute or Donate
- License
- Basic time synchronization over all the servers
- Going through sector border and "teleporting" between sectors
- Super basic chat system
- Simple, but powerful API
- Performance (Kryonet / Kryo instead of Redis)
- Configuration (with JSON support)
- Security
- Advanced logging systems
- ActionBar support
- Built-in fast MySQL connection and API (using HikariCP)
- Possibilities
- Create a border (from MC1.8) on each sector
- More informations in
PacketPlayerInfo
- More default packets to use
- Multiple center sectors to improve performance (sector channel system)
- Better time/weather system
The project actually contains two projects - linker and system. Both are really important for everything to work.
You put OpenSectorLinker
on each of your sectors,
and OpenSectorSystem
on your BungeeCord (or one of it's forks) server.
Both contain useful configurations, and you should check them!
Remember, you want to unlock port 23904
and 23905
(or other you specify in configuration)
because they're used to send data over the sector network.
No! Absolutely not. It's main advantage over it's competitors is - no additional libraries or unconventional databases needed. It's just... plug & play.
You just need a MySQL server which is pretty standard today.
You need to rewrite them or ask developers to do it for you. There's no way to make this differently. 70% of plugins need to be synchronized over the sector network to work properly.
Fortunatelly, OpenSectors
provide super easy API which
makes this task much easier for end-user.
You can edit existing plugins to make them work with OpenSectors
which will require minimal Java and networking knowledge.
But! I can edit or write plugins which will support OpenSectors
for you! You need to contact me directly though.
Configuration is pretty simple. The main problem is to provide
valid coordinates for your sectors. You can use ready to use configuration
below, or create your own. Unfortunatelly, OpenSectors
don't provide
ANY of location scaling systems or similar currently.
server-id: 0
proxy-address: "127.0.0.1"
proxy-port-tcp: 23904
proxy-port-udp: 23905
proxy-connection-timeout: 5000
bufferSize: 8192 # Buffer size, increase it to send larger packets
kryonet-logging: NONE
auth-password: "CHANGE_THIS_TO_YOUR_PASSWORD___IT_NEEDS_TO_BE_SECURE!"
# Messages
sector-welcome-message:
- ''
- ' &aWelcome on sector &7%name%&a (id: &7%id%&a)'
- ''
sector-break-message: '&cYou can not destroy blocks that close to the sector border!'
sector-place-message: '&cYou can not build blocks that close to the sector border!'
sector-ignite-message: '&cYou can not ignite blocks that close to the sector border!'
sector-bucket-message: '&cYou can not pour out water/lava that close to the sector border!'
sector-border-close: '&2&lSector border: &f&l%distance%'
{
"password": "CHANGE_THIS_TO_YOUR_PASSWORD___IT_NEEDS_TO_BE_SECURE!",
"sectors": 5,
"portTCP": 23904,
"portUDP": 23905,
"bufferSize": 8192,
"bukkitTimeFrequency": 500,
"bukkitTimeIncremental": 10,
"border": 3000,
"defaultChatFormat": "&7{PLAYER}&8: &f{MESSAGE}",
"sqlController": {
"host": "localhost",
"port": 3306,
"user": "root",
"password": "",
"database": "opensectors",
"useDefaultSql": true
},
"serverControllers": [
{
"id": 0,
"offset": 0,
"port": 25566,
"name": "center",
"minX": -250.0,
"minZ": -250.0,
"maxX": 250.0,
"maxZ": 250.0
},
{
"id": 1,
"offset": 2,
"port": 25567,
"name": "n",
"minX": -250.0,
"minZ": -2000.0,
"maxX": 2000.0,
"maxZ": -250.0
},
{
"id": 2,
"offset": 3,
"port": 25568,
"name": "s",
"minX": -2000.0,
"minZ": 250.0,
"maxX": 250.0,
"maxZ": 2000.0
},
{
"id": 3,
"offset": 4,
"port": 25569,
"name": "e",
"minX": 250.0,
"minZ": -250.0,
"maxX": 2000.0,
"maxZ": 2000.0
},
{
"id": 4,
"offset": 5,
"port": 25570,
"name": "w",
"minX": -2000.0,
"minZ": -2000.0,
"maxX": -250.0,
"maxZ": 250.0
}
]
}
servers:
center:
motd: OpenSectors
address: localhost:25566
restricted: false
n:
motd: North Sector
address: localhost:25567
restricted: false
s:
motd: South Sector
address: localhost:25568
restricted: false
w:
motd: West Sector
address: localhost:25569
restricted: false
e:
motd: East Sector
address: localhost:25570
restricted: false
Yes, that's what you wanted, don't ya? :D
API is fairly easy, and with Javadocs/code on github it's even easier.
But I will tell you how to do basic things using OpenSectors
API system.
API will be definitely richer in the future, remember that!
Add this code to your maven dependencies.
<repositories>
<repository>
<id>socketbyte-repo</id>
<url>http://repo.socketbyte.pl/repository/nexus-releases</url>
</repository>
</repositories>
<dependencies>
<dependency>
<groupId>pl.socketbyte</groupId>
<artifactId>OpenSectorLinker</artifactId>
<version>1.2</version>
<scope>provided</scope>
</dependency>
</dependencies>
And of course Spigot/Bukkit engine too!
or this code when you make plugin for system:
<repositories>
<repository>
<id>socketbyte-repo</id>
<url>http://repo.socketbyte.pl/repository/nexus-releases</url>
</repository>
</repositories>
<dependencies>
<dependency>
<groupId>pl.socketbyte</groupId>
<artifactId>OpenSectorSystem</artifactId>
<version>1.4</version>
<scope>provided</scope>
</dependency>
</dependencies>
There is few types of API calls you can make. Firstly, I would want you to know about packet catching and sending. It is very important in the long term. You want to send a lot of packets and data, and catch it!
You can register your own Packet
extended classes easily,
and send them over the network (and/or catch them in the listener)
It is fairly easy, look;
PacketExtender<PacketExampleTest> packetExtender = SectorAPI.createPacketExtender(PacketExampleTest.class);
packetExtender.setPacketAdapter((connection, packet) ->
System.out.println("Received PacketExampleTest from the proxy server."));
In order this to work, you need to register this class on system plugin too.
SectorAPI.register(PacketExampleTest.class);
or just create PacketExtender
like above.
What is PacketExampleTest
? It's a class, which extend Packet
class.
public class PacketExampleTest extends Packet {
private String test;
public PacketExampleTest() {
}
public String getTest() {
return test;
}
public void setTest(String test) {
this.test = test;
}
}
Remember, it needs to be absolutely the same on both sides. Congratulations! Now you can send it using:
PacketExampleTest exampleTest = new PacketExampleTest();
exampleTest.setTest("Hello, World!");
SectorAPI.sendTCP(exampleTest);
You can also send UDP packets.
SectorAPI.sendUDP(exampleTest);
You can also register something like pub/sub in Redis. What do I mean?
You can register specific channels and send custom payload packet which contain some data.
It is less convinient than using PacketExtender
but it can be better sometimes.
SectorAPI.registerPayloadChannel("TEST_CHANNEL", (connection, packet) -> {
System.out.println("Received PacketCustomPayload (TEST_CHANNEL) as a LINKER.");
System.out.println(packet);
});
You can do the same on the system side. (You don't need to register anything!) Now you can send something, like
PacketCustomPayload customPayload = new PacketCustomPayload();
customPayload.setChannel("TEST_CHANNEL");
customPayload.setData(new Object[] { "Hello", "World!" });
SectorAPI.sendTCP(customPayload);
This is... MySQL API! How awesome, right? Firstly, you need to make a Query packet.
PacketQuery query = new PacketQuery();
query.setQuery("INSERT INTO something VALUES(?, ?)");
...set replacements...
query.addReplacement(1, "a nice value");
query.addReplacement(2, "even nicer value");
... and send them!
SectorAPI.sendTCP(query);
Congratulations, you executed a query on the system side
and now your values are in the global database! Amazing!
But, what if you want a result...? like... ResultSet
?
It works with this API too!
Make an execute query
PacketQueryExecute queryExecute = new PacketQueryExecute();
queryExecute.setQuery("SELECT * FROM something WHERE fancier=?");
queryExecute.addReplacement(1, "even nicer value");
... and!
SectorAPI.sendTCP(queryExecute, packetQueryExecute -> {
SerializableResultSet resultSet = packetQueryExecute.getResultSet();
while (resultSet.next()) {
System.out.println("Received from SQL: " + resultSet.getString(0)
+ " -> " + resultSet.getString(1));
}
});
You create a query which contains an interface which is your callback.
Confusing, right? Not really, it just returns you a modified packet
with SerializableResultSet
(something which is similar to real ResultSet
but cached and serializable)
That's all for the basic API part! You have some other functions but for that you need to check javadocs!
You can get javadocs here: https://socketbyte.pl/javadocs/
(and select proper project)
The newest API allows you to set the database (like MariaDB, PostgreSQL, OrientDB or SQLite)
How to do it? It is very simple.
Set useDefaultSql
value in JSON (system) configuration to false
.
This code needs to be in onEnable()
of your addon plugin.
HikariMySQL hikari = new HikariMySQL();
hikari.setHost("localhost");
hikari.setDatabase("database");
hikari.setPort(3306);
hikari.setPassword("");
hikari.setUser("root");
hikari.apply();
HikariManager.INSTANCE.connect();
Congratulations, you did a standard MySQL connection. But what about others? I've prepared 4 most popular SQL configurations for Hikari.
Just change the class like this:
HikariMariaDb hikari = new HikariMariaDb();
hikari.setHost("localhost");
hikari.setDatabase("database");
hikari.setPort(3306);
hikari.setPassword("");
hikari.setUser("root");
hikari.apply();
HikariManager.INSTANCE.connect();
You can do the same with:
new HikariSQLite();
new HikariPostgreSQL();
Remember, SQLite doesn't need port, password or user. You need only to set database, which is your file name.
What about basic table creation? There's an API for that too!
hikariMySQL.setTableWork(connection -> {
try {
PreparedStatement statement = connection.prepareStatement(
"CREATE TABLE IF NOT EXISTS...");
statement.executeUpdate();
statement.close();
} catch (SQLException e) {
e.printStackTrace();
}
finally {
try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
});
Simple as that, then activate it by using:
HikariManager.INSTANCE.createBasicTables();
You can also create your own database configurations for other SQL distributions.
You can do that in two ways. Do a custom class which extend HikariExtender
, or use HikariWrapper
.
Make a class and do extends HikariExtender
, apply, and set everything.
You can access HikariDataSource
by using getDataSource()
and every other
setting using getPassword()
or getHost()
.
Example (SQLite class):
public class HikariSQLite extends HikariExtender {
@Override
protected void connect() {
HikariDataSource dataSource = getDataSource();
String database = getDatabase();
// Do everything you want here.
dataSource.setJdbcUrl("jdbc:sqlite:" + database + ".db");
dataSource.setDriverClassName("org.sqlite.JDBC");
dataSource.setPoolName("HikariSQLite");
dataSource.setMaxLifetime(0);
dataSource.setMaxLifetime(60000);
dataSource.setIdleTimeout(45000);
dataSource.setMaximumPoolSize(20);
Properties properties = new Properties();
properties.put("driverType", "thin");
dataSource.setDataSourceProperties(properties);
}
}
HikariWrapper is a class that allows you to do everything like above, but without creating a new class. Why? Because you can!
Example:
HikariWrapper hikariWrapper = new HikariWrapper(hikariWrapper1 -> {
HikariDataSource dataSource = hikariWrapper1.getDataSource();
dataSource.setJdbcUrl("...");
dataSource.setDriverClassName("...");
});
hikariWrapper.apply();
HikariManager.INSTANCE.connect();
More about HikariCP
here:
https://github.com/brettwooldridge/HikariCP
It's easy to use system for sending, and instantly receiving packets (and doing something with them)
It's based on a simple Callback<T>
and CompletableFuture<T>
which makes it really convinient.
Basic usage:
PacketPlayerState state = new PacketPlayerState();
state.setPlayerName("...AnyPlayerName...");
SectorAPI.sendTCP(state, new Callback<PacketPlayerState>() {
@Override
public void execute(PacketPlayerState packetPlayerState) {
// Your callback (information from the proxy)
System.out.println("Sector: " + packetPlayerState.getServerId());
System.out.println("Is Online: " + packetPlayerState.isOnline());
System.out.println("UniqueID: " + packetPlayerState.getPlayerUniqueId());
}
});
This system also works on PacketQueryExecute
.
More packets based on callback system to come soon!
There's pretty basic task system, but it allows you to create tasks which are synchronized over the network, so they're executed simultaneously.
To create a task, use code below:
SectorAPI.createTask(0,
() -> System.out.println("Synchronized task with id:0"),
0, 1, TimeUnit.SECONDS);
The first parameter (0
) is a task id. Each task
should have a different one!
Second parameter is a runnable, which is your code.
Then you have (0
) which is initialDelay
.
Then is a period
which is the rate of the task.
And the last parameter is TimeUnit
which doesn't need much explaining.
There is also a singleshot task system, you can execute a code
simultaneously on all sectors using task.send()
.
To prepare a singleshot task use:
SynchronizedTask task = SectorAPI.prepareSingleShot(1,
() -> System.out.println("Single shot task executed!"));
And then send it using
task.send();
You can do that as many times as you want.
Remember that if you use task system, you need to install your plugin on all sectors.
The new WIP feature of OpenSectors
API.
It allows you to create synchronized lists or maps over the network.
It's super easy to use because of direct List<E>
or Map<K, V>
implementation.
List<String> list = new SynchronizedList<>(0);
Map<String, String> map = new SynchronizedMap<>(0);
The parameter is an ID, it needs to be unique for each list/map. Then, you just use them like normal lists or maps.
Remember, this is a Work In Progress feature. It can be bugged, especially maps (!).
More about wrapp here.
You can easily integrate Wrapp to OpenSectors. How? Let me explain.
Start with registering an adapter:
WrapperFactory wrapperFactory = new WrapperFactory(); // REUSE THAT
SectorAPI.registerPayloadChannel("MYOBJECT_WRAPP_CHANNEL", (connection, packet) -> {
PacketWrapper<MyObject> packet = (PacketWrapper)packet;
Wrapper<MyObject> wrapper = packet.getWrapper();
MyObject myObject = new MyObject();
wrapperFactory.read(wrapper, myObject);
// congratz, your object is now set!
});
Then you can send it from Linker
using:
MyObject objectToSave = new MyObject(); // it's not serializable
WrapperFactory wrapperFactory = new WrapperFactory(); // REUSE THAT
wrapperFactory.register(MyObject.class); // optional to improve speed
Wrapper<MyObject> wrapper = wrapperFactory.write(objectToSave);
PacketWrapper<MyObject> packet = new PacketWrapper<>();
packet.setChannel("MYOBJECT_WRAPP_CHANNEL");
packet.setData(new Object[] { "additional data" });
packet.setWrapper(wrapper);
SectorAPI.sendTCP(packet);
// Create packet
PacketItemTransfer packet = new PacketItemTransfer();
// Set receiver (all == every player on every sector)
packet.setReceiver(Receiver.ALL);
// Set itemstack as SerializableItem
packet.setItemStack(new SerializableItem(new ItemStack(...)));
// Create packet
PacketSendMessage packet = new PacketSendMessage();
// Set receiver (player == only one player)
packet.setReceiver(Receiver.PLAYER, "uniqueId");
// Set message and it's type
packet.setMessage(MessageType.ACTION_BAR, "&6&lHello, World!");
PacketPlayerTransfer packet = new PacketPlayerTransfer();
// Set the player
packet.setPlayerUniqueId("uniqueId");
// Set player info (not required)
packet.setPlayerInfo(new PacketPlayerInfo(player));
// Set server id
packet.setServerId(1);
PacketPlayerTeleport packet = new PacketPlayerTeleport();
// Set player
packet.setPlayerUniqueId("uniqueId");
// Set target (optional)
packet.setTargetUniqueId("uniqueId");
// Or set coords
packet.setLocation(250, 70, 250);
PacketPlayerState packet = new PacketPlayerState();
packet.setPlayerName("...");
SectorAPI.sendTCP(packet, packetPlayerState -> {
// do what you want with the callback
});
More to come soon!
Yes! Of course you can contribute in OpenSectors
Either helping with code, or supporting the project with donation.
You can donate here:
You will be mentioned in the README.md
file as soon as you donate something. (Every donation really appreciated)
Project is licensed under Creative Commons License (CC BY-NC 3.0)
as you're
not permitted to use open-source version of this project in commercial applications.