Don't allow two players of the same name to join (except from testing worlds)

fixes #1636
This commit is contained in:
IntegratedQuantum 2025-07-01 22:47:36 +02:00
parent 54aab15a4e
commit dd506e197e
3 changed files with 37 additions and 24 deletions

View File

@ -669,9 +669,11 @@ pub const World = struct { // MARK: World
.name = "client",
.milliTime = std.time.milliTimestamp(),
};
errdefer self.conn.deinit();
self.itemDrops.init(main.globalAllocator);
network.Protocols.handShake.clientSide(self.conn, settings.playerName);
errdefer self.itemDrops.deinit();
try network.Protocols.handShake.clientSide(self.conn, settings.playerName);
main.Window.setMouseGrabbed(true);

View File

@ -644,18 +644,13 @@ pub const Protocols = struct {
pub const handShake = struct {
pub const id: u8 = 1;
pub const asynchronous = false;
const stepStart: u8 = 0;
const stepUserData: u8 = 1;
const stepAssets: u8 = 2;
const stepServerData: u8 = 3;
pub const stepComplete: u8 = 255;
fn receive(conn: *Connection, reader: *utils.BinaryReader) !void {
const newState = try reader.readInt(u8);
if(conn.handShakeState.load(.monotonic) < newState) {
const newState = try reader.readEnum(Connection.HandShakeState);
if(@intFromEnum(conn.handShakeState.load(.monotonic)) < @intFromEnum(newState)) {
conn.handShakeState.store(newState, .monotonic);
switch(newState) {
stepUserData => {
.userData => {
const zon = ZonElement.parseFromString(main.stackAllocator, null, reader.remaining);
defer zon.deinit(main.stackAllocator);
const name = zon.get([]const u8, "name", "unnamed");
@ -673,7 +668,7 @@ pub const Protocols = struct {
defer dir.close();
var arrayList = main.List(u8).init(main.stackAllocator);
defer arrayList.deinit();
arrayList.append(stepAssets);
arrayList.append(@intFromEnum(Connection.HandShakeState.assets));
try utils.Compression.pack(dir, arrayList.writer());
conn.send(.fast, id, arrayList.items);
}
@ -689,30 +684,27 @@ pub const Protocols = struct {
zonObject.put("toolPalette", main.server.world.?.toolPalette.storeToZon(main.stackAllocator));
zonObject.put("biomePalette", main.server.world.?.biomePalette.storeToZon(main.stackAllocator));
const outData = zonObject.toStringEfficient(main.stackAllocator, &[1]u8{stepServerData});
const outData = zonObject.toStringEfficient(main.stackAllocator, &[1]u8{@intFromEnum(Connection.HandShakeState.serverData)});
defer main.stackAllocator.free(outData);
conn.send(.fast, id, outData);
conn.handShakeState.store(stepServerData, .monotonic);
conn.handShakeState.store(.serverData, .monotonic);
main.server.connect(conn.user.?);
},
stepAssets => {
.assets => {
std.log.info("Received assets.", .{});
std.fs.cwd().deleteTree("serverAssets") catch {}; // Delete old assets.
var dir = try std.fs.cwd().makeOpenPath("serverAssets", .{});
defer dir.close();
try utils.Compression.unpack(dir, reader.remaining);
},
stepServerData => {
.serverData => {
const zon = ZonElement.parseFromString(main.stackAllocator, null, reader.remaining);
defer zon.deinit(main.stackAllocator);
try conn.manager.world.?.finishHandshake(zon);
conn.handShakeState.store(stepComplete, .monotonic);
conn.handShakeState.store(.complete, .monotonic);
conn.handShakeWaiting.broadcast(); // Notify the waiting client thread.
},
stepComplete => {},
else => {
std.log.err("Unknown state in HandShakeProtocol {}", .{newState});
},
.start, .complete => {},
}
} else {
// Ignore packages that refer to an unexpected state. Normally those might be packages that were resent by the other side.
@ -720,21 +712,22 @@ pub const Protocols = struct {
}
pub fn serverSide(conn: *Connection) void {
conn.handShakeState.store(stepStart, .monotonic);
conn.handShakeState.store(.start, .monotonic);
}
pub fn clientSide(conn: *Connection, name: []const u8) void {
pub fn clientSide(conn: *Connection, name: []const u8) !void {
const zonObject = ZonElement.initObject(main.stackAllocator);
defer zonObject.deinit(main.stackAllocator);
zonObject.putOwnedString("version", settings.version);
zonObject.putOwnedString("name", name);
const prefix = [1]u8{stepUserData};
const prefix = [1]u8{@intFromEnum(Connection.HandShakeState.userData)};
const data = zonObject.toStringEfficient(main.stackAllocator, &prefix);
defer main.stackAllocator.free(data);
conn.send(.fast, id, data);
conn.mutex.lock();
conn.handShakeWaiting.wait(&conn.mutex);
if(conn.connectionState.load(.monotonic) == .disconnectDesired) return error.DisconnectedByServer;
conn.mutex.unlock();
}
};
@ -1819,6 +1812,14 @@ pub const Connection = struct { // MARK: Connection
disconnectDesired,
};
const HandShakeState = enum(u8) {
start = 0,
userData = 1,
assets = 2,
serverData = 3,
complete = 255,
};
// MARK: fields
manager: *ConnectionManager,
@ -1847,7 +1848,7 @@ pub const Connection = struct { // MARK: Connection
relativeIdleTime: i64 = 0,
connectionState: Atomic(ConnectionState),
handShakeState: Atomic(u8) = .init(Protocols.handShake.stepStart),
handShakeState: Atomic(HandShakeState) = .init(.start),
handShakeWaiting: std.Thread.Condition = std.Thread.Condition{},
lastConnection: i64,
@ -2223,6 +2224,7 @@ pub const Connection = struct { // MARK: Connection
if(self.user) |user| {
main.server.disconnect(user);
} else {
self.handShakeWaiting.broadcast();
main.exitToMenu(undefined);
}
std.log.info("Disconnected", .{});

View File

@ -503,6 +503,15 @@ pub fn connectInternal(user: *User) void {
// TODO: addEntity(player);
const userList = getUserListAndIncreaseRefCount(main.stackAllocator);
defer freeUserListAndDecreaseRefCount(main.stackAllocator, userList);
// Check if a user with that name is already present
if(!world.?.testingMode) {
for(userList) |other| {
if(std.mem.eql(u8, other.name, user.name)) {
user.conn.disconnect();
return;
}
}
}
// Let the other clients know about this new one.
{
const zonArray = main.ZonElement.initArray(main.stackAllocator);
@ -538,7 +547,7 @@ pub fn connectInternal(user: *User) void {
userMutex.lock();
users.append(user);
userMutex.unlock();
user.conn.handShakeState.store(main.network.Protocols.handShake.stepComplete, .monotonic);
user.conn.handShakeState.store(.complete, .monotonic);
}
pub fn messageFrom(msg: []const u8, source: *User) void { // MARK: message