mirror of
https://github.com/PixelGuys/Cubyz.git
synced 2025-08-03 11:17:05 -04:00
584 lines
20 KiB
Zig
584 lines
20 KiB
Zig
const std = @import("std");
|
|
const Atomic = std.atomic.Value;
|
|
|
|
const main = @import("main");
|
|
const chunk = main.chunk;
|
|
const network = main.network;
|
|
const Connection = network.Connection;
|
|
const ConnectionManager = network.ConnectionManager;
|
|
const utils = main.utils;
|
|
const vec = main.vec;
|
|
const Vec3d = vec.Vec3d;
|
|
const Vec3f = vec.Vec3f;
|
|
const Vec3i = vec.Vec3i;
|
|
const BinaryReader = main.utils.BinaryReader;
|
|
const BinaryWriter = main.utils.BinaryWriter;
|
|
const Blueprint = main.blueprint.Blueprint;
|
|
const Mask = main.blueprint.Mask;
|
|
const NeverFailingAllocator = main.heap.NeverFailingAllocator;
|
|
const CircularBufferQueue = main.utils.CircularBufferQueue;
|
|
|
|
pub const world_zig = @import("world.zig");
|
|
pub const ServerWorld = world_zig.ServerWorld;
|
|
pub const terrain = @import("terrain/terrain.zig");
|
|
pub const Entity = @import("Entity.zig");
|
|
pub const storage = @import("storage.zig");
|
|
|
|
const command = @import("command/_command.zig");
|
|
|
|
pub const WorldEditData = struct {
|
|
const maxWorldEditHistoryCapacity: u32 = 1024;
|
|
|
|
selectionPosition1: ?Vec3i = null,
|
|
selectionPosition2: ?Vec3i = null,
|
|
clipboard: ?Blueprint = null,
|
|
undoHistory: History,
|
|
redoHistory: History,
|
|
mask: ?Mask = null,
|
|
|
|
const History = struct {
|
|
changes: CircularBufferQueue(Value),
|
|
|
|
const Value = struct {
|
|
blueprint: Blueprint,
|
|
position: Vec3i,
|
|
message: []const u8,
|
|
|
|
pub fn init(blueprint: Blueprint, position: Vec3i, message: []const u8) Value {
|
|
return .{.blueprint = blueprint, .position = position, .message = main.globalAllocator.dupe(u8, message)};
|
|
}
|
|
pub fn deinit(self: Value) void {
|
|
main.globalAllocator.free(self.message);
|
|
self.blueprint.deinit(main.globalAllocator);
|
|
}
|
|
};
|
|
pub fn init() History {
|
|
return .{.changes = .init(main.globalAllocator, maxWorldEditHistoryCapacity)};
|
|
}
|
|
pub fn deinit(self: *History) void {
|
|
self.clear();
|
|
self.changes.deinit();
|
|
}
|
|
pub fn clear(self: *History) void {
|
|
while(self.changes.popFront()) |item| item.deinit();
|
|
}
|
|
pub fn push(self: *History, value: Value) void {
|
|
if(self.changes.reachedCapacity()) {
|
|
if(self.changes.popFront()) |oldValue| oldValue.deinit();
|
|
}
|
|
|
|
self.changes.pushBack(value);
|
|
}
|
|
pub fn pop(self: *History) ?Value {
|
|
return self.changes.popBack();
|
|
}
|
|
};
|
|
pub fn init() WorldEditData {
|
|
return .{.undoHistory = History.init(), .redoHistory = History.init()};
|
|
}
|
|
pub fn deinit(self: *WorldEditData) void {
|
|
if(self.clipboard != null) {
|
|
self.clipboard.?.deinit(main.globalAllocator);
|
|
}
|
|
self.undoHistory.deinit();
|
|
self.redoHistory.deinit();
|
|
if(self.mask) |mask| {
|
|
mask.deinit(main.globalAllocator);
|
|
}
|
|
}
|
|
};
|
|
|
|
pub const User = struct { // MARK: User
|
|
const maxSimulationDistance = 8;
|
|
const simulationSize = 2*maxSimulationDistance;
|
|
const simulationMask = simulationSize - 1;
|
|
conn: *Connection = undefined,
|
|
player: Entity = .{},
|
|
timeDifference: utils.TimeDifference = .{},
|
|
interpolation: utils.GenericInterpolation(3) = undefined,
|
|
lastTime: i16 = undefined,
|
|
lastSaveTime: i64 = 0,
|
|
name: []const u8 = "",
|
|
renderDistance: u16 = undefined,
|
|
clientUpdatePos: Vec3i = .{0, 0, 0},
|
|
receivedFirstEntityData: bool = false,
|
|
isLocal: bool = false,
|
|
id: u32 = 0, // TODO: Use entity id.
|
|
// TODO: ipPort: []const u8,
|
|
loadedChunks: [simulationSize][simulationSize][simulationSize]*@import("world.zig").EntityChunk = undefined,
|
|
lastRenderDistance: u16 = 0,
|
|
lastPos: Vec3i = @splat(0),
|
|
gamemode: std.atomic.Value(main.game.Gamemode) = .init(.creative),
|
|
worldEditData: WorldEditData = undefined,
|
|
|
|
lastSentBiomeId: u32 = 0xffffffff,
|
|
|
|
inventoryClientToServerIdMap: std.AutoHashMap(u32, u32) = undefined,
|
|
inventory: ?u32 = null,
|
|
handInventory: ?u32 = null,
|
|
|
|
connected: Atomic(bool) = .init(true),
|
|
|
|
refCount: Atomic(u32) = .init(1),
|
|
|
|
mutex: std.Thread.Mutex = .{},
|
|
|
|
pub fn initAndIncreaseRefCount(manager: *ConnectionManager, ipPort: []const u8) !*User {
|
|
const self = main.globalAllocator.create(User);
|
|
errdefer main.globalAllocator.destroy(self);
|
|
self.* = .{};
|
|
self.inventoryClientToServerIdMap = .init(main.globalAllocator.allocator);
|
|
self.interpolation.init(@ptrCast(&self.player.pos), @ptrCast(&self.player.vel));
|
|
self.conn = try Connection.init(manager, ipPort, self);
|
|
self.increaseRefCount();
|
|
self.worldEditData = .init();
|
|
network.Protocols.handShake.serverSide(self.conn);
|
|
return self;
|
|
}
|
|
|
|
pub fn deinit(self: *User) void {
|
|
std.debug.assert(self.refCount.load(.monotonic) == 0);
|
|
|
|
main.items.Inventory.Sync.ServerSide.disconnectUser(self);
|
|
std.debug.assert(self.inventoryClientToServerIdMap.count() == 0); // leak
|
|
self.inventoryClientToServerIdMap.deinit();
|
|
|
|
world.?.savePlayer(self) catch |err| {
|
|
std.log.err("Failed to save player: {s}", .{@errorName(err)});
|
|
return;
|
|
};
|
|
|
|
if(self.inventory) |inv| main.items.Inventory.Sync.ServerSide.destroyExternallyManagedInventory(inv);
|
|
if(self.handInventory) |inv| main.items.Inventory.Sync.ServerSide.destroyExternallyManagedInventory(inv);
|
|
|
|
self.worldEditData.deinit();
|
|
|
|
self.unloadOldChunk(.{0, 0, 0}, 0);
|
|
self.conn.deinit();
|
|
main.globalAllocator.free(self.name);
|
|
main.globalAllocator.destroy(self);
|
|
}
|
|
|
|
pub fn increaseRefCount(self: *User) void {
|
|
const prevVal = self.refCount.fetchAdd(1, .monotonic);
|
|
std.debug.assert(prevVal != 0);
|
|
}
|
|
|
|
pub fn decreaseRefCount(self: *User) void {
|
|
const prevVal = self.refCount.fetchSub(1, .monotonic);
|
|
std.debug.assert(prevVal != 0);
|
|
if(prevVal == 1) {
|
|
self.deinit();
|
|
}
|
|
}
|
|
|
|
var freeId: u32 = 0;
|
|
pub fn initPlayer(self: *User, name: []const u8) void {
|
|
self.id = freeId;
|
|
freeId += 1;
|
|
|
|
self.name = main.globalAllocator.dupe(u8, name);
|
|
world.?.findPlayer(self);
|
|
}
|
|
|
|
fn simArrIndex(x: i32) usize {
|
|
return @intCast(x >> chunk.chunkShift & simulationMask);
|
|
}
|
|
|
|
fn unloadOldChunk(self: *User, newPos: Vec3i, newRenderDistance: u16) void {
|
|
const lastBoxStart = (self.lastPos -% @as(Vec3i, @splat(self.lastRenderDistance*chunk.chunkSize))) & ~@as(Vec3i, @splat(chunk.chunkMask));
|
|
const lastBoxEnd = (self.lastPos +% @as(Vec3i, @splat(self.lastRenderDistance*chunk.chunkSize))) +% @as(Vec3i, @splat(chunk.chunkSize - 1)) & ~@as(Vec3i, @splat(chunk.chunkMask));
|
|
const newBoxStart = (newPos -% @as(Vec3i, @splat(newRenderDistance*chunk.chunkSize))) & ~@as(Vec3i, @splat(chunk.chunkMask));
|
|
const newBoxEnd = (newPos +% @as(Vec3i, @splat(newRenderDistance*chunk.chunkSize))) +% @as(Vec3i, @splat(chunk.chunkSize - 1)) & ~@as(Vec3i, @splat(chunk.chunkMask));
|
|
// Clear all chunks not inside the new box:
|
|
var x: i32 = lastBoxStart[0];
|
|
while(x != lastBoxEnd[0]) : (x +%= chunk.chunkSize) {
|
|
const inXDistance = x -% newBoxStart[0] >= 0 and x -% newBoxEnd[0] < 0;
|
|
var y: i32 = lastBoxStart[1];
|
|
while(y != lastBoxEnd[1]) : (y +%= chunk.chunkSize) {
|
|
const inYDistance = y -% newBoxStart[1] >= 0 and y -% newBoxEnd[1] < 0;
|
|
var z: i32 = lastBoxStart[2];
|
|
while(z != lastBoxEnd[2]) : (z +%= chunk.chunkSize) {
|
|
const inZDistance = z -% newBoxStart[2] >= 0 and z -% newBoxEnd[2] < 0;
|
|
if(!inXDistance or !inYDistance or !inZDistance) {
|
|
self.loadedChunks[simArrIndex(x)][simArrIndex(y)][simArrIndex(z)].decreaseRefCount();
|
|
self.loadedChunks[simArrIndex(x)][simArrIndex(y)][simArrIndex(z)] = undefined;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
fn loadNewChunk(self: *User, newPos: Vec3i, newRenderDistance: u16) void {
|
|
const lastBoxStart = (self.lastPos -% @as(Vec3i, @splat(self.lastRenderDistance*chunk.chunkSize))) & ~@as(Vec3i, @splat(chunk.chunkMask));
|
|
const lastBoxEnd = (self.lastPos +% @as(Vec3i, @splat(self.lastRenderDistance*chunk.chunkSize))) +% @as(Vec3i, @splat(chunk.chunkSize - 1)) & ~@as(Vec3i, @splat(chunk.chunkMask));
|
|
const newBoxStart = (newPos -% @as(Vec3i, @splat(newRenderDistance*chunk.chunkSize))) & ~@as(Vec3i, @splat(chunk.chunkMask));
|
|
const newBoxEnd = (newPos +% @as(Vec3i, @splat(newRenderDistance*chunk.chunkSize))) +% @as(Vec3i, @splat(chunk.chunkSize - 1)) & ~@as(Vec3i, @splat(chunk.chunkMask));
|
|
// Clear all chunks not inside the new box:
|
|
var x: i32 = newBoxStart[0];
|
|
while(x != newBoxEnd[0]) : (x +%= chunk.chunkSize) {
|
|
const inXDistance = x -% lastBoxStart[0] >= 0 and x -% lastBoxEnd[0] < 0;
|
|
var y: i32 = newBoxStart[1];
|
|
while(y != newBoxEnd[1]) : (y +%= chunk.chunkSize) {
|
|
const inYDistance = y -% lastBoxStart[1] >= 0 and y -% lastBoxEnd[1] < 0;
|
|
var z: i32 = newBoxStart[2];
|
|
while(z != newBoxEnd[2]) : (z +%= chunk.chunkSize) {
|
|
const inZDistance = z -% lastBoxStart[2] >= 0 and z -% lastBoxEnd[2] < 0;
|
|
if(!inXDistance or !inYDistance or !inZDistance) {
|
|
self.loadedChunks[simArrIndex(x)][simArrIndex(y)][simArrIndex(z)] = @TypeOf(world.?.chunkManager).getOrGenerateEntityChunkAndIncreaseRefCount(.{.wx = x, .wy = y, .wz = z, .voxelSize = 1});
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
fn loadUnloadChunks(self: *User) void {
|
|
const newPos: Vec3i = @as(Vec3i, @intFromFloat(self.player.pos)) +% @as(Vec3i, @splat(chunk.chunkSize/2)) & ~@as(Vec3i, @splat(chunk.chunkMask));
|
|
const newRenderDistance = main.settings.simulationDistance;
|
|
if(@reduce(.Or, newPos != self.lastPos) or newRenderDistance != self.lastRenderDistance) {
|
|
self.unloadOldChunk(newPos, newRenderDistance);
|
|
self.loadNewChunk(newPos, newRenderDistance);
|
|
self.lastRenderDistance = newRenderDistance;
|
|
self.lastPos = newPos;
|
|
}
|
|
}
|
|
|
|
pub fn update(self: *User) void {
|
|
self.mutex.lock();
|
|
defer self.mutex.unlock();
|
|
var time = @as(i16, @truncate(std.time.milliTimestamp())) -% main.settings.entityLookback;
|
|
time -%= self.timeDifference.difference.load(.monotonic);
|
|
self.interpolation.update(time, self.lastTime);
|
|
self.lastTime = time;
|
|
|
|
const saveTime = std.time.milliTimestamp();
|
|
if(saveTime -% self.lastSaveTime > 5000) {
|
|
world.?.savePlayer(self) catch |err| {
|
|
std.log.err("Failed to save player {s}: {s}", .{self.name, @errorName(err)});
|
|
};
|
|
self.lastSaveTime = saveTime;
|
|
}
|
|
|
|
self.loadUnloadChunks();
|
|
}
|
|
|
|
pub fn receiveData(self: *User, reader: *BinaryReader) !void {
|
|
self.mutex.lock();
|
|
defer self.mutex.unlock();
|
|
const position: [3]f64 = try reader.readVec(Vec3d);
|
|
const velocity: [3]f64 = try reader.readVec(Vec3d);
|
|
const rotation: [3]f32 = try reader.readVec(Vec3f);
|
|
self.player.rot = rotation;
|
|
const time = try reader.readInt(i16);
|
|
self.timeDifference.addDataPoint(time);
|
|
self.interpolation.updatePosition(&position, &velocity, time);
|
|
}
|
|
|
|
pub fn sendMessage(self: *User, comptime fmt: []const u8, args: anytype) void {
|
|
const msg = std.fmt.allocPrint(main.stackAllocator.allocator, fmt, args) catch unreachable;
|
|
defer main.stackAllocator.free(msg);
|
|
self.sendRawMessage(msg);
|
|
}
|
|
fn sendRawMessage(self: *User, msg: []const u8) void {
|
|
main.network.Protocols.chat.send(self.conn, msg);
|
|
}
|
|
};
|
|
|
|
pub const updatesPerSec: u32 = 20;
|
|
const updateNanoTime: u32 = 1000000000/20;
|
|
|
|
pub var world: ?*ServerWorld = null;
|
|
var userMutex: std.Thread.Mutex = .{};
|
|
var users: main.List(*User) = undefined;
|
|
var userDeinitList: main.utils.ConcurrentQueue(*User) = undefined;
|
|
var userConnectList: main.utils.ConcurrentQueue(*User) = undefined;
|
|
|
|
pub var connectionManager: *ConnectionManager = undefined;
|
|
|
|
pub var running: std.atomic.Value(bool) = .init(false);
|
|
var lastTime: i128 = undefined;
|
|
|
|
pub var thread: ?std.Thread = null;
|
|
|
|
fn init(name: []const u8, singlePlayerPort: ?u16) void { // MARK: init()
|
|
std.debug.assert(world == null); // There can only be one world.
|
|
command.init();
|
|
users = .init(main.globalAllocator);
|
|
userDeinitList = .init(main.globalAllocator, 16);
|
|
userConnectList = .init(main.globalAllocator, 16);
|
|
lastTime = std.time.nanoTimestamp();
|
|
connectionManager = ConnectionManager.init(main.settings.defaultPort, false) catch |err| {
|
|
std.log.err("Couldn't create socket: {s}", .{@errorName(err)});
|
|
@panic("Could not open Server.");
|
|
}; // TODO Configure the second argument in the server settings.
|
|
|
|
main.items.Inventory.Sync.ServerSide.init();
|
|
|
|
world = ServerWorld.init(name, null) catch |err| {
|
|
std.log.err("Failed to create world: {s}", .{@errorName(err)});
|
|
@panic("Can't create world.");
|
|
};
|
|
world.?.generate() catch |err| {
|
|
std.log.err("Failed to generate world: {s}", .{@errorName(err)});
|
|
@panic("Can't generate world.");
|
|
};
|
|
if(singlePlayerPort) |port| blk: {
|
|
const ipString = std.fmt.allocPrint(main.stackAllocator.allocator, "127.0.0.1:{}", .{port}) catch unreachable;
|
|
defer main.stackAllocator.free(ipString);
|
|
const user = User.initAndIncreaseRefCount(connectionManager, ipString) catch |err| {
|
|
std.log.err("Cannot create singleplayer user {s}", .{@errorName(err)});
|
|
break :blk;
|
|
};
|
|
defer user.decreaseRefCount();
|
|
user.isLocal = true;
|
|
}
|
|
}
|
|
|
|
fn deinit() void {
|
|
users.clearAndFree();
|
|
while(userDeinitList.popFront()) |user| {
|
|
user.deinit();
|
|
}
|
|
userDeinitList.deinit();
|
|
userConnectList.deinit();
|
|
for(connectionManager.connections.items) |conn| {
|
|
conn.user.?.decreaseRefCount();
|
|
}
|
|
connectionManager.deinit();
|
|
connectionManager = undefined;
|
|
|
|
main.items.Inventory.Sync.ServerSide.deinit();
|
|
|
|
if(world) |_world| {
|
|
_world.deinit();
|
|
}
|
|
world = null;
|
|
command.deinit();
|
|
}
|
|
|
|
pub fn getUserListAndIncreaseRefCount(allocator: main.heap.NeverFailingAllocator) []*User {
|
|
userMutex.lock();
|
|
defer userMutex.unlock();
|
|
const result = allocator.dupe(*User, users.items);
|
|
for(result) |user| {
|
|
user.increaseRefCount();
|
|
}
|
|
return result;
|
|
}
|
|
|
|
pub fn freeUserListAndDecreaseRefCount(allocator: main.heap.NeverFailingAllocator, list: []*User) void {
|
|
for(list) |user| {
|
|
user.decreaseRefCount();
|
|
}
|
|
allocator.free(list);
|
|
}
|
|
|
|
fn getInitialEntityList(allocator: main.heap.NeverFailingAllocator) []const u8 {
|
|
// Send the entity updates:
|
|
var initialList: []const u8 = undefined;
|
|
const list = main.ZonElement.initArray(main.stackAllocator);
|
|
defer list.deinit(main.stackAllocator);
|
|
list.array.append(.null);
|
|
const itemDropList = world.?.itemDropManager.getInitialList(main.stackAllocator);
|
|
list.array.appendSlice(itemDropList.array.items);
|
|
itemDropList.array.items.len = 0;
|
|
itemDropList.deinit(main.stackAllocator);
|
|
initialList = list.toStringEfficient(allocator, &.{});
|
|
return initialList;
|
|
}
|
|
|
|
fn update() void { // MARK: update()
|
|
world.?.update();
|
|
|
|
while(userConnectList.popFront()) |user| {
|
|
connectInternal(user);
|
|
}
|
|
|
|
const userList = getUserListAndIncreaseRefCount(main.stackAllocator);
|
|
defer freeUserListAndDecreaseRefCount(main.stackAllocator, userList);
|
|
for(userList) |user| {
|
|
user.update();
|
|
}
|
|
|
|
// Send the entity data:
|
|
const itemData = world.?.itemDropManager.getPositionAndVelocityData(main.stackAllocator);
|
|
defer main.stackAllocator.free(itemData);
|
|
|
|
var entityData: main.List(main.entity.EntityNetworkData) = .init(main.stackAllocator);
|
|
defer entityData.deinit();
|
|
|
|
for(userList) |user| {
|
|
const id = user.id; // TODO
|
|
entityData.append(.{
|
|
.id = id,
|
|
.pos = user.player.pos,
|
|
.vel = user.player.vel,
|
|
.rot = user.player.rot,
|
|
});
|
|
}
|
|
for(userList) |user| {
|
|
main.network.Protocols.entityPosition.send(user.conn, user.player.pos, entityData.items, itemData);
|
|
}
|
|
|
|
for(userList) |user| {
|
|
const pos = @as(Vec3i, @intFromFloat(user.player.pos));
|
|
const biomeId = world.?.getBiome(pos[0], pos[1], pos[2]).paletteId;
|
|
if(biomeId != user.lastSentBiomeId) {
|
|
user.lastSentBiomeId = biomeId;
|
|
main.network.Protocols.genericUpdate.sendBiome(user.conn, biomeId);
|
|
}
|
|
}
|
|
|
|
while(userDeinitList.popFront()) |user| {
|
|
user.decreaseRefCount();
|
|
}
|
|
}
|
|
|
|
pub fn start(name: []const u8, port: ?u16) void {
|
|
main.initThreadLocals();
|
|
defer main.deinitThreadLocals();
|
|
std.debug.assert(!running.load(.monotonic)); // There can only be one server.
|
|
init(name, port);
|
|
defer deinit();
|
|
running.store(true, .release);
|
|
while(running.load(.monotonic)) {
|
|
main.heap.GarbageCollection.syncPoint();
|
|
const newTime = std.time.nanoTimestamp();
|
|
if(newTime -% lastTime < updateNanoTime) {
|
|
std.Thread.sleep(@intCast(lastTime +% updateNanoTime -% newTime));
|
|
lastTime +%= updateNanoTime;
|
|
} else {
|
|
std.log.warn("The server is lagging behind by {d:.1} ms", .{@as(f32, @floatFromInt(newTime -% lastTime -% updateNanoTime))/1000000.0});
|
|
lastTime = newTime;
|
|
}
|
|
update();
|
|
}
|
|
}
|
|
|
|
pub fn stop() void {
|
|
running.store(false, .monotonic);
|
|
}
|
|
|
|
pub fn disconnect(user: *User) void { // MARK: disconnect()
|
|
if(!user.connected.load(.unordered)) return;
|
|
removePlayer(user);
|
|
userDeinitList.pushBack(user);
|
|
user.connected.store(false, .unordered);
|
|
}
|
|
|
|
pub fn removePlayer(user: *User) void { // MARK: removePlayer()
|
|
if(!user.connected.load(.unordered)) return;
|
|
|
|
const foundUser = blk: {
|
|
userMutex.lock();
|
|
defer userMutex.unlock();
|
|
for(users.items, 0..) |other, i| {
|
|
if(other == user) {
|
|
_ = users.swapRemove(i);
|
|
break :blk true;
|
|
}
|
|
}
|
|
break :blk false;
|
|
};
|
|
if(!foundUser) return;
|
|
|
|
sendMessage("{s}§#ffff00 left", .{user.name});
|
|
// Let the other clients know about that this new one left.
|
|
const zonArray = main.ZonElement.initArray(main.stackAllocator);
|
|
defer zonArray.deinit(main.stackAllocator);
|
|
zonArray.array.append(.{.int = user.id});
|
|
const data = zonArray.toStringEfficient(main.stackAllocator, &.{});
|
|
defer main.stackAllocator.free(data);
|
|
const userList = getUserListAndIncreaseRefCount(main.stackAllocator);
|
|
defer freeUserListAndDecreaseRefCount(main.stackAllocator, userList);
|
|
for(userList) |other| {
|
|
main.network.Protocols.entity.send(other.conn, data);
|
|
}
|
|
}
|
|
|
|
pub fn connect(user: *User) void {
|
|
userConnectList.pushBack(user);
|
|
}
|
|
|
|
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);
|
|
defer zonArray.deinit(main.stackAllocator);
|
|
const entityZon = main.ZonElement.initObject(main.stackAllocator);
|
|
entityZon.put("id", user.id);
|
|
entityZon.put("name", user.name);
|
|
zonArray.array.append(entityZon);
|
|
const data = zonArray.toStringEfficient(main.stackAllocator, &.{});
|
|
defer main.stackAllocator.free(data);
|
|
for(userList) |other| {
|
|
main.network.Protocols.entity.send(other.conn, data);
|
|
}
|
|
}
|
|
{ // Let this client know about the others:
|
|
const zonArray = main.ZonElement.initArray(main.stackAllocator);
|
|
defer zonArray.deinit(main.stackAllocator);
|
|
for(userList) |other| {
|
|
const entityZon = main.ZonElement.initObject(main.stackAllocator);
|
|
entityZon.put("id", other.id);
|
|
entityZon.put("name", other.name);
|
|
zonArray.array.append(entityZon);
|
|
}
|
|
const data = zonArray.toStringEfficient(main.stackAllocator, &.{});
|
|
defer main.stackAllocator.free(data);
|
|
if(user.connected.load(.unordered)) main.network.Protocols.entity.send(user.conn, data);
|
|
}
|
|
const initialList = getInitialEntityList(main.stackAllocator);
|
|
main.network.Protocols.entity.send(user.conn, initialList);
|
|
main.stackAllocator.free(initialList);
|
|
sendMessage("{s}§#ffff00 joined", .{user.name});
|
|
|
|
userMutex.lock();
|
|
users.append(user);
|
|
userMutex.unlock();
|
|
user.conn.handShakeState.store(.complete, .monotonic);
|
|
}
|
|
|
|
pub fn messageFrom(msg: []const u8, source: *User) void { // MARK: message
|
|
if(msg[0] == '/') { // Command.
|
|
if(world.?.allowCheats) {
|
|
std.log.info("User \"{s}\" executed command \"{s}\"", .{source.name, msg}); // TODO use color \033[0;32m
|
|
command.execute(msg[1..], source);
|
|
} else {
|
|
source.sendRawMessage("Commands are not allowed because cheats are disabled");
|
|
}
|
|
} else {
|
|
main.server.sendMessage("[{s}§#ffffff] {s}", .{source.name, msg});
|
|
}
|
|
}
|
|
|
|
fn sendRawMessage(msg: []const u8) void {
|
|
chatMutex.lock();
|
|
defer chatMutex.unlock();
|
|
std.log.info("Chat: {s}", .{msg}); // TODO use color \033[0;32m
|
|
const userList = getUserListAndIncreaseRefCount(main.stackAllocator);
|
|
defer freeUserListAndDecreaseRefCount(main.stackAllocator, userList);
|
|
for(userList) |user| {
|
|
user.sendRawMessage(msg);
|
|
}
|
|
}
|
|
|
|
var chatMutex: std.Thread.Mutex = .{};
|
|
pub fn sendMessage(comptime fmt: []const u8, args: anytype) void {
|
|
const msg = std.fmt.allocPrint(main.stackAllocator.allocator, fmt, args) catch unreachable;
|
|
defer main.stackAllocator.free(msg);
|
|
sendRawMessage(msg);
|
|
}
|