Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve custom tasmod server #177

Merged
merged 60 commits into from
Nov 16, 2023
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
Show all changes
60 commits
Select commit Hold shift + click to select a range
483afb3
Add lombok and refactor logger
PancakeTAS May 27, 2023
fc56586
Implement asynchronous client
PancakeTAS May 27, 2023
cbdf96e
Implement server and update client
PancakeTAS May 27, 2023
e0a2f1d
Implement authentication system
PancakeTAS Jun 14, 2023
56e8f85
Launch server and client on launch
PancakeTAS Jun 14, 2023
00f116f
Start moving packets to new system
PancakeTAS Jun 14, 2023
c51f1b6
Continue with packets
PancakeTAS Jun 14, 2023
fc261b9
Mark fields as final and preinitialize buffers
PancakeTAS Aug 8, 2023
475f113
Fix loggers
PancakeTAS Aug 8, 2023
bf3a131
Don't manually calculate sizes
PancakeTAS Aug 8, 2023
e2ee5b2
Update loom
PancakeTAS Aug 10, 2023
bd3069b
Rewrite to enums
PancakeTAS Aug 10, 2023
749a60e
Update method calls too
PancakeTAS Aug 10, 2023
682683a
Delombokified code
ScribbleTAS Aug 11, 2023
e220f5b
Adding packet handler interfaces and helpers for sending packets
ScribbleTAS Aug 12, 2023
690b8c3
WIP Packet rewrite
ScribbleTAS Aug 13, 2023
71a015e
Added tests for bufferbuilder and server
ScribbleTAS Aug 13, 2023
b78e1b3
Adding tests for the Client/Server
ScribbleTAS Aug 14, 2023
98aae99
Continued rewriting packets
ScribbleTAS Aug 15, 2023
ef8c655
Continued with packets
ScribbleTAS Aug 17, 2023
a9d3622
Finishing packets, renaming and removing classes
ScribbleTAS Aug 19, 2023
620ab76
Increased ttl for ServerTest
ScribbleTAS Aug 19, 2023
c6a02e1
Disabled ServerTest for now, doesn't seem to work with github...
ScribbleTAS Aug 19, 2023
4937f58
Added scheduler for opening the main menu
ScribbleTAS Aug 21, 2023
9cc75c5
Added logger as static import
ScribbleTAS Aug 22, 2023
b4c0c3a
Changed default port, added static import for logger
ScribbleTAS Aug 22, 2023
58a577e
Implementing tickrate packets, registering packet handlers
ScribbleTAS Aug 23, 2023
57c89fe
Documentation and small code improvements
ScribbleTAS Aug 27, 2023
a68cd0c
Fixed requesting motion, simplified NBTTagCompound sending
ScribbleTAS Sep 1, 2023
d3262e5
Fixing and discovering savestating issues
ScribbleTAS Sep 2, 2023
41d430b
Added defaults to infohud, added reset button, switched middleclick a…
ScribbleTAS Sep 3, 2023
59ab7ca
Changing to usernames instead off uuid, fixing BufferUnderflow crashes
ScribbleTAS Sep 3, 2023
9697829
Rearranging registering in main methods and documentation 🚂
ScribbleTAS Sep 5, 2023
8f1eb9d
Fixed labels going off screen in InfoHud
ScribbleTAS Sep 10, 2023
8f5f046
Networking for PlaybackControllerClient and TASstateServer to Playbac…
ScribbleTAS Sep 10, 2023
54d4e0b
More documentation for packets
ScribbleTAS Sep 10, 2023
55ae4a6
Updating loader version to 0.14.23
ScribbleTAS Oct 5, 2023
6ec4a45
Adding log4j config to server console
ScribbleTAS Oct 5, 2023
8b30d81
Testing multiplayer connections
ScribbleTAS Oct 5, 2023
eaefa65
Rewriting connection things
ScribbleTAS Oct 9, 2023
5a502a2
Fixing server closing
ScribbleTAS Oct 15, 2023
55b3e19
Fixed connecting to the wrong server
ScribbleTAS Oct 15, 2023
22ce325
Fixed tickadvance not working as intended
ScribbleTAS Oct 20, 2023
95c7065
Added disconnect packet and check for timeout
ScribbleTAS Oct 22, 2023
6455551
Fixed Redirect throwing errors
ScribbleTAS Oct 22, 2023
38eb555
Fixed client and server timing out in tickrate 0
ScribbleTAS Oct 22, 2023
8e235d0
Fixed gui controls crashing in tr 0
ScribbleTAS Oct 22, 2023
9b7fcca
Updated junit
ScribbleTAS Oct 22, 2023
9e4ab6d
Add property for "integrated" server
ScribbleTAS Oct 24, 2023
149dc1b
Removed timeout system
ScribbleTAS Oct 24, 2023
24739c2
Added a trace statement for network packets and it's contents
ScribbleTAS Nov 12, 2023
e8a7dc2
Networking fixes
ScribbleTAS Nov 12, 2023
7fd795b
[Savestates] Fixed a rare crash with gui screens
ScribbleTAS Nov 12, 2023
c60195e
Trying to fix savestates
ScribbleTAS Nov 12, 2023
2cb0fdc
[Savestates] Remove WASLOADING state
ScribbleTAS Nov 13, 2023
8614dbb
[Event] Drafting new event firing
ScribbleTAS Nov 13, 2023
d2bfe4b
Revert "[Event] Drafting new event firing"
ScribbleTAS Nov 15, 2023
e85bc87
[Networking] Added config option for automatically connecting to a cu…
ScribbleTAS Nov 15, 2023
e013634
[Networking] Documentation fixes 🚂
ScribbleTAS Nov 15, 2023
f69a6f2
[Networking] Finishing documentation 🚂 for packet ids
ScribbleTAS Nov 16, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,10 @@ configurations {

// dependencies
dependencies {
// annotation processor
compileOnly 'org.projectlombok:lombok:1.18.28'
annotationProcessor 'org.projectlombok:lombok:1.18.28'

// tasmod dependencies
embed group: 'com.dselent', name: 'bigarraylist', version: '1.0'
compileOnly group: 'com.minecrafttas', name: 'killtherng', version: '2.0'
Expand Down
278 changes: 278 additions & 0 deletions src/main/java/com/minecrafttas/server/Client.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,278 @@
package com.minecrafttas.server;

import static com.minecrafttas.tasmod.TASmod.LOGGER;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousSocketChannel;
import java.nio.channels.CompletionHandler;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.Future;
import java.util.function.Consumer;

import com.minecrafttas.tasmod.TASmod;
import com.minecrafttas.tasmod.TASmodClient;
import com.minecrafttas.tasmod.savestates.client.InputSavestatesHandler;
import com.minecrafttas.tasmod.savestates.client.gui.GuiSavestateSavingScreen;
import com.minecrafttas.tasmod.savestates.server.chunkloading.SavestatesChunkControl;
import com.minecrafttas.tasmod.savestates.server.motion.ClientMotionServer;
import com.minecrafttas.tasmod.savestates.server.motion.ClientMotionServer.Saver;
import com.minecrafttas.tasmod.tickratechanger.TickrateChangerServer.State;
import com.minecrafttas.tasmod.ticksync.TickSyncClient;
import com.minecrafttas.tasmod.ticksync.TickSyncServer;

import lombok.Getter;
import lombok.var;
import net.minecraft.client.Minecraft;

public class Client {

private AsynchronousSocketChannel socket;
private ByteBuffer writeBuffer;
private ByteBuffer readBuffer;
private Future<Integer> future;
private Map<Integer, Consumer<ByteBuffer>> handlers = new HashMap<>();

@Getter
private UUID id;

/**
* Create and connect socket
* @param host Host
* @param port Port
* @throws Exception Unable to connect
*/
public Client(String host, int port) throws Exception {
LOGGER.info("Connecting tasmod server to {}:{}", host, port);
this.socket = AsynchronousSocketChannel.open();
this.socket.connect(new InetSocketAddress(host, port)).get();
this.createHandlers();
this.registerClientsidePacketHandlers();
LOGGER.info("Connected to tasmod server");
}

/**
* Fork existing socket
* @param socket Socket
*/
public Client(AsynchronousSocketChannel socket) {
this.socket = socket;
this.createHandlers();
this.registerServersidePacketHandlers();
}

/**
* Create read/write buffers and handlers for socket
*/
private void createHandlers() {
// create buffers
this.writeBuffer = ByteBuffer.allocate(1024*1024);
this.readBuffer = ByteBuffer.allocate(1024*1024);

// create input handler
this.readBuffer.limit(4);
this.socket.read(this.readBuffer, null, new CompletionHandler<Integer, Object>() {

@Override
public void completed(Integer result, Object attachment) {
try {
// read rest of packet
readBuffer.flip();
int lim = readBuffer.getInt();
readBuffer.clear().limit(lim);
socket.read(readBuffer).get();

// handle packet
readBuffer.position(0);
handle(readBuffer);

// read packet header again
readBuffer.clear().limit(4);
socket.read(readBuffer, null, this);
} catch (Throwable exc) {
LOGGER.error("Unable to read packet {}", exc);
}
}

@Override
public void failed(Throwable exc, Object attachment) {
LOGGER.error("Unable to read packet {}", exc);
}

});
}

/**
* Write packet to server
* @param buf Packet
* @throws Exception Networking exception
*/
public void write(ByteBuffer buf) throws Exception {
// wait for previous buffer to send
if (this.future != null && !this.future.isDone())
this.future.get();

// prepare buffer
this.writeBuffer.clear();
this.writeBuffer.putInt(buf.capacity());
this.writeBuffer.put((ByteBuffer) buf.position(0));
this.writeBuffer.flip();

// send buffer async
this.future = this.socket.write(this.writeBuffer);
}

/**
* Try to close socket
* @throws IOException Unable to close
*/
public void close() throws IOException {
if (this.socket == null || !this.socket.isOpen()) {
LOGGER.warn("Tried to close dead socket");
return;
}

this.socket.close();
}

/**
* Register packet handlers for packets received on the client
*/
private void registerClientsidePacketHandlers() {
// packet 2: tick client
this.handlers.put(2, buf -> TickSyncClient.onPacket());

// packet 5: change client tickrate
this.handlers.put(5, buf -> TASmodClient.tickratechanger.changeClientTickrate(buf.getFloat()));

// packet 8: advance tick on clients
this.handlers.put(8, buf -> TASmodClient.tickratechanger.advanceClientTick());

// packet 9: change tickrate on client
this.handlers.put(9, buf -> TASmodClient.tickratechanger.changeClientTickrate(buf.getFloat()));

// packet 10: savestate inputs client
this.handlers.put(10, buf -> {
try {
var nameBytes = new byte[buf.getInt()];
buf.get(nameBytes);
var name = new String(nameBytes);
InputSavestatesHandler.savestate(name);
} catch (Exception e) {
TASmod.LOGGER.error("Exception occured during input savestate: {}", e);
}
});

// packet 11: close GuiSavestateScreen on client
this.handlers.put(11, buf ->
Minecraft.getMinecraft().addScheduledTask(() -> {
var mc = Minecraft.getMinecraft();
if (!(mc.currentScreen instanceof GuiSavestateSavingScreen))
mc.displayGuiScreen(new GuiSavestateSavingScreen());
else
mc.displayGuiScreen(null);
})
);

// packet 12: loadstate inputs client
this.handlers.put(12, buf -> {
try {
var nameBytes = new byte[buf.getInt()];
buf.get(nameBytes);
var name = new String(nameBytes);
InputSavestatesHandler.loadstate(name);
} catch (Exception e) {
TASmod.LOGGER.error("Exception occured during input loadstate: {}", e);
}
});

// packet 13: unload chunks on client
this.handlers.put(13, buf ->
Minecraft.getMinecraft().addScheduledTask(() ->
SavestatesChunkControl.unloadAllClientChunks()));

// packet 14: request client motion
this.handlers.put(14, buf -> {
var player = Minecraft.getMinecraft().player;
if (player != null) {
if (!(Minecraft.getMinecraft().currentScreen instanceof GuiSavestateSavingScreen))
Minecraft.getMinecraft().displayGuiScreen(new GuiSavestateSavingScreen());

try {
// packet 15: send client motion to server
TASmodClient.client.write(ByteBuffer.allocate(4 + 8+8+8 + 4+4+4 + 1 + 4).putInt(15)
.putDouble(player.motionX).putDouble(player.motionY).putDouble(player.motionZ)
.putFloat(player.moveForward).putFloat(player.moveVertical).putFloat(player.moveStrafing)
.put((byte) (player.isSprinting() ? 1 : 0))
.putFloat(player.jumpMovementFactor)
);
} catch (Exception e) {
TASmod.LOGGER.error("Unable to send packet to server: {}", e);
}
}
});
}
PancakeTAS marked this conversation as resolved.
Show resolved Hide resolved

/**
* Register packet handlers for packets received on the server
*/
private void registerServersidePacketHandlers() {
// packet 1: authentication packet
this.handlers.put(1, buf -> {
this.id = new UUID(buf.getLong(), buf.getLong());
LOGGER.info("Client authenticated: " + this.id);
});

// packet 3: notify server of tick pass
this.handlers.put(3, buf -> TickSyncServer.onPacket(this.id));

// packet 4: request tickrate change
this.handlers.put(4, buf -> TASmod.tickratechanger.changeTickrate(buf.getFloat()));

// packet 6: tickrate zero toggle
this.handlers.put(6, buf -> {
var state = State.fromShort(buf.getShort());
if (state == State.PAUSE)
TASmod.tickratechanger.pauseGame(true);
else if (state == State.UNPAUSE)
TASmod.tickratechanger.pauseGame(false);
else if (state == State.TOGGLE)
TASmod.tickratechanger.togglePause();
});

// packet 7: request tick advance
this.handlers.put(7, buf -> {
if (TASmod.tickratechanger.ticksPerSecond == 0)
TASmod.tickratechanger.advanceTick();
});

// packet 15: send client motion to server
this.handlers.put(15, buf -> ClientMotionServer.getMotion().put(TASmod.getServerInstance().getPlayerList().getPlayerByUUID(this.id), new Saver(buf.getDouble(), buf.getDouble(), buf.getDouble(), buf.getFloat(), buf.getFloat(), buf.getFloat(), buf.get() == 1 ? true : false, buf.getFloat())));
}

/**
* Sends then authentication packet to the server
* @param id Unique ID
* @throws Exception Unable to send packet
*/
public void authenticate(UUID id) throws Exception {
this.id = id;

ByteBuffer buf = ByteBuffer.allocate(4+8+8);
buf.putInt(1);
buf.putLong(id.getMostSignificantBits());
buf.putLong(id.getLeastSignificantBits());
this.write(buf);
}

private void handle(ByteBuffer buf) {
var id = buf.getInt();
this.handlers.getOrDefault(id, _buf -> {
LOGGER.error("Received invalid packet: {}", id);
}).accept(buf);
}

}
90 changes: 90 additions & 0 deletions src/main/java/com/minecrafttas/server/Server.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
package com.minecrafttas.server;

import static com.minecrafttas.tasmod.TASmod.LOGGER;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousServerSocketChannel;
import java.nio.channels.AsynchronousSocketChannel;
import java.nio.channels.CompletionHandler;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;

import lombok.Getter;
import lombok.var;

public class Server {

private AsynchronousServerSocketChannel socket;

@Getter
private List<Client> clients;

/**
* Create and bind socket
* @param port Port
* @throws Exception Unable to bind
*/
public Server(int port) throws Exception {
// create connection
LOGGER.info("Creating tasmod server on {}", port);
this.socket = AsynchronousServerSocketChannel.open();
this.socket.bind(new InetSocketAddress(port));

// create connection handler
this.clients = new ArrayList<>();
this.socket.accept(null, new CompletionHandler<AsynchronousSocketChannel, Object>() {

@Override
public void completed(AsynchronousSocketChannel clientSocket, Object attachment) {
clients.add(new Client(clientSocket));
socket.accept(null, this);
}

@Override
public void failed(Throwable exc, Object attachment) {
LOGGER.error("Unable to accept client {}", exc);
}
});

LOGGER.info("TASmod server created");
}

/**
* Write packet to all clients
* @param buf Packet
* @throws Exception Networking exception
*/
public void writeAll(ByteBuffer buf) throws Exception {
for (var client : this.clients)
client.write(buf);
}

/**
* Try to close socket
* @throws IOException Unable to close
*/
public void close() throws IOException {
if (this.socket == null || !this.socket.isOpen()) {
LOGGER.warn("Tried to close dead socket");
return;
}

this.socket.close();
}

/**
* Get client from UUID
* @param uniqueID UUID
*/
public Client getClient(UUID uniqueID) {
for (var client : this.clients)
if (client.getId().equals(uniqueID))
return client;

return null;
}

}
Loading