1 /** 2 Copyright: Copyright (c) 2016 Andrey Penechko. 3 License: $(WEB boost.org/LICENSE_1_0.txt, Boost License 1.0). 4 Authors: Andrey Penechko. 5 */ 6 module voxelman.login.plugin; 7 8 import std.experimental.logger; 9 import netlib; 10 import pluginlib; 11 import voxelman.math; 12 13 import voxelman.core.config; 14 import voxelman.core.events; 15 import voxelman.net.events; 16 import voxelman.core.packets; 17 import voxelman.net.packets; 18 import voxelman.world.storage.coordinates; 19 20 import voxelman.config.configmanager : ConfigManager, ConfigOption; 21 import voxelman.command.plugin; 22 import voxelman.eventdispatcher.plugin; 23 import voxelman.net.plugin; 24 import voxelman.world.plugin; 25 import voxelman.world.clientworld; 26 import voxelman.graphics.plugin; 27 28 import voxelman.login.clientinfo; 29 30 shared static this() 31 { 32 pluginRegistry.regClientPlugin(new ClientDbClient); 33 pluginRegistry.regServerPlugin(new ClientDbServer); 34 } 35 36 struct ThisClientLoggedInEvent { 37 ClientId thisClientId; 38 } 39 40 final class ClientDbClient : IPlugin 41 { 42 private: 43 EventDispatcherPlugin evDispatcher; 44 GraphicsPlugin graphics; 45 NetClientPlugin connection; 46 ClientWorld clientWorld; 47 48 ConfigOption nicknameOpt; 49 50 public: 51 ClientId thisClientId; 52 string[ClientId] clientNames; 53 bool isSpawned = false; 54 55 // IPlugin stuff 56 mixin IdAndSemverFrom!(voxelman.login.plugininfo); 57 58 override void registerResources(IResourceManagerRegistry resmanRegistry) 59 { 60 ConfigManager config = resmanRegistry.getResourceManager!ConfigManager; 61 nicknameOpt = config.registerOption!string("name", "Player"); 62 } 63 64 override void init(IPluginManager pluginman) 65 { 66 graphics = pluginman.getPlugin!GraphicsPlugin; 67 68 evDispatcher = pluginman.getPlugin!EventDispatcherPlugin; 69 evDispatcher.subscribeToEvent(&onSendClientSettingsEvent); 70 evDispatcher.subscribeToEvent(&handleThisClientDisconnected); 71 72 clientWorld = pluginman.getPlugin!ClientWorld; 73 74 connection = pluginman.getPlugin!NetClientPlugin; 75 connection.registerPacketHandler!SessionInfoPacket(&handleSessionInfoPacket); 76 connection.registerPacketHandler!ClientLoggedInPacket(&handleUserLoggedInPacket); 77 connection.registerPacketHandler!ClientLoggedOutPacket(&handleUserLoggedOutPacket); 78 connection.registerPacketHandler!ClientPositionPacket(&handleClientPositionPacket); 79 connection.registerPacketHandler!SpawnPacket(&handleSpawnPacket); 80 connection.registerPacketHandler!GameStartPacket(&handleGameStartPacket); 81 } 82 83 void onSendClientSettingsEvent(ref SendClientSettingsEvent event) 84 { 85 connection.send(LoginPacket(nicknameOpt.get!string)); 86 } 87 88 void handleThisClientDisconnected(ref ThisClientDisconnectedEvent event) 89 { 90 isSpawned = false; 91 } 92 93 void handleGameStartPacket(ubyte[] packetData, ClientId clientId) 94 { 95 evDispatcher.postEvent(ThisClientConnectedEvent()); 96 evDispatcher.postEvent(SendClientSettingsEvent()); 97 connection.send(GameStartPacket()); 98 } 99 100 void handleUserLoggedInPacket(ubyte[] packetData, ClientId clientId) 101 { 102 auto newUser = unpackPacket!ClientLoggedInPacket(packetData); 103 clientNames[newUser.clientId] = newUser.clientName; 104 infof("%s has connected", newUser.clientName); 105 evDispatcher.postEvent(ClientLoggedInEvent(clientId)); 106 } 107 108 void handleUserLoggedOutPacket(ubyte[] packetData, ClientId clientId) 109 { 110 auto packet = unpackPacket!ClientLoggedOutPacket(packetData); 111 infof("%s has disconnected", clientName(packet.clientId)); 112 evDispatcher.postEvent(ClientLoggedOutEvent(clientId)); 113 clientNames.remove(packet.clientId); 114 } 115 116 void handleSessionInfoPacket(ubyte[] packetData, ClientId clientId) 117 { 118 auto loginInfo = unpackPacket!SessionInfoPacket(packetData); 119 120 clientNames = loginInfo.clientNames; 121 thisClientId = loginInfo.yourId; 122 evDispatcher.postEvent(ThisClientLoggedInEvent(thisClientId)); 123 } 124 125 void handleClientPositionPacket(ubyte[] packetData, ClientId peer) 126 { 127 auto packet = unpackPacket!ClientPositionPacket(packetData); 128 //tracef("Received ClientPositionPacket(%s, %s, %s, %s)", 129 // packet.pos, packet.heading, packet.dimention, packet.positionKey); 130 131 nansToZero(packet.pos); 132 graphics.camera.position = vec3(packet.pos); 133 134 nansToZero(packet.heading); 135 graphics.camera.setHeading(vec2(packet.heading)); 136 137 clientWorld.setCurrentDimention(packet.dimention, packet.positionKey); 138 } 139 140 void handleSpawnPacket(ubyte[] packetData, ClientId peer) 141 { 142 auto packet = unpackPacket!SpawnPacket(packetData); 143 isSpawned = true; 144 clientWorld.updateObserverPosition(); 145 } 146 147 string clientName(ClientId clientId) 148 { 149 import std.string : format; 150 return clientId in clientNames ? clientNames[clientId] : format("? %s", clientId); 151 } 152 } 153 154 final class ClientDbServer : IPlugin 155 { 156 private: 157 EventDispatcherPlugin evDispatcher; 158 NetServerPlugin connection; 159 ServerWorld serverWorld; 160 161 public: 162 ClientInfo*[ClientId] clients; 163 164 // IPlugin stuff 165 mixin IdAndSemverFrom!(voxelman.login.plugininfo); 166 167 override void init(IPluginManager pluginman) 168 { 169 evDispatcher = pluginman.getPlugin!EventDispatcherPlugin; 170 connection = pluginman.getPlugin!NetServerPlugin; 171 serverWorld = pluginman.getPlugin!ServerWorld; 172 173 evDispatcher.subscribeToEvent(&handleClientConnected); 174 evDispatcher.subscribeToEvent(&handleClientDisconnected); 175 176 connection.registerPacketHandler!LoginPacket(&handleLoginPacket); 177 connection.registerPacketHandler!ViewRadiusPacket(&handleViewRadius); 178 connection.registerPacketHandler!ClientPositionPacket(&handleClientPosition); 179 connection.registerPacketHandler!GameStartPacket(&handleGameStartPacket); 180 181 auto commandPlugin = pluginman.getPlugin!CommandPluginServer; 182 commandPlugin.registerCommand("spawn", &onSpawn); 183 commandPlugin.registerCommand("dim", &changeDimentionCommand); 184 commandPlugin.registerCommand("add_active", &onAddActive); 185 commandPlugin.registerCommand("remove_active", &onRemoveActive); 186 } 187 188 void onAddActive(CommandParams params) { 189 auto cwp = clients[params.source].chunk; 190 serverWorld.activeChunks.add(cwp); 191 infof("add active %s", cwp); 192 } 193 194 void onRemoveActive(CommandParams params) { 195 auto cwp = clients[params.source].chunk; 196 serverWorld.activeChunks.remove(cwp); 197 infof("remove active %s", cwp); 198 } 199 200 void onSpawn(CommandParams params) 201 { 202 ClientInfo* info = clients.get(params.source, null); 203 if(info is null) return; 204 info.pos = START_POS; 205 info.heading = vec2(0,0); 206 info.dimention = 0; 207 info.positionKey = 0; 208 connection.sendTo(params.source, ClientPositionPacket(info.pos.arrayof, 209 info.heading.arrayof, info.dimention, info.positionKey)); 210 updateObserverBox(info); 211 } 212 213 bool isLoggedIn(ClientId clientId) 214 { 215 ClientInfo* clientInfo = clients[clientId]; 216 return clientInfo.isLoggedIn; 217 } 218 219 bool isSpawned(ClientId clientId) 220 { 221 ClientInfo* clientInfo = clients[clientId]; 222 return clientInfo.isSpawned; 223 } 224 225 string[ClientId] clientNames() 226 { 227 string[ClientId] names; 228 foreach(id, client; clients) { 229 names[id] = client.name; 230 } 231 232 return names; 233 } 234 235 string clientName(ClientId clientId) 236 { 237 import std.string : format; 238 auto cl = clients.get(clientId, null); 239 return cl ? cl.name : format("%s", clientId); 240 } 241 242 auto loggedInClients() 243 { 244 import std.algorithm : filter, map; 245 return clients.byKeyValue.filter!(a=>a.value.isLoggedIn).map!(a=>a.value.id); 246 } 247 248 void spawnClient(vec3 pos, vec2 heading, ushort dimention, ClientId clientId) 249 { 250 ClientInfo* info = clients[clientId]; 251 info.pos = pos; 252 info.heading = heading; 253 info.dimention = dimention; 254 ++info.positionKey; 255 connection.sendTo(clientId, ClientPositionPacket(pos.arrayof, heading.arrayof, dimention, info.positionKey)); 256 connection.sendTo(clientId, SpawnPacket()); 257 updateObserverBox(info); 258 } 259 260 void handleClientConnected(ref ClientConnectedEvent event) 261 { 262 clients[event.clientId] = new ClientInfo(event.clientId); 263 } 264 265 void handleClientDisconnected(ref ClientDisconnectedEvent event) 266 { 267 infof("%s %s disconnected", event.clientId, 268 clients[event.clientId].name); 269 270 connection.sendToAll(ClientLoggedOutPacket(event.clientId)); 271 clients.remove(event.clientId); 272 } 273 274 void changeDimentionCommand(CommandParams params) 275 { 276 import std.conv : to, ConvException; 277 278 ClientInfo* info = clients[params.source]; 279 if (info.isSpawned) 280 { 281 if (params.args.length > 1) 282 { 283 auto dim = to!DimentionId(params.args[1]); 284 if (dim == info.dimention) 285 return; 286 287 info.dimention = dim; 288 ++info.positionKey; 289 updateObserverBox(info); 290 291 connection.sendTo(params.source, ClientPositionPacket(info.pos.arrayof, 292 info.heading.arrayof, info.dimention, info.positionKey)); 293 } 294 } 295 } 296 297 void updateObserverBox(ClientInfo* info) 298 { 299 if (info.isSpawned) { 300 serverWorld.chunkObserverManager.changeObserverBox(info.id, info.chunk, info.viewRadius); 301 } 302 } 303 304 void handleLoginPacket(ubyte[] packetData, ClientId clientId) 305 { 306 auto packet = unpackPacket!LoginPacket(packetData); 307 ClientInfo* info = clients[clientId]; 308 info.name = packet.clientName; 309 info.id = clientId; 310 info.isLoggedIn = true; 311 312 infof("%s %s logged in", clientId, clients[clientId].name); 313 314 connection.sendTo(clientId, SessionInfoPacket(clientId, clientNames)); 315 connection.sendToAllExcept(clientId, ClientLoggedInPacket(clientId, packet.clientName)); 316 317 evDispatcher.postEvent(ClientLoggedInEvent(clientId)); 318 } 319 320 void handleGameStartPacket(ubyte[] packetData, ClientId clientId) 321 { 322 if (isLoggedIn(clientId)) 323 { 324 ClientInfo* info = clients[clientId]; 325 info.isSpawned = true; 326 spawnClient(info.pos, info.heading, info.dimention, clientId); 327 } 328 } 329 330 void handleViewRadius(ubyte[] packetData, ClientId clientId) 331 { 332 import std.algorithm : clamp; 333 auto packet = unpackPacket!ViewRadiusPacket(packetData); 334 ClientInfo* info = clients[clientId]; 335 info.viewRadius = clamp(packet.viewRadius, 336 MIN_VIEW_RADIUS, MAX_VIEW_RADIUS); 337 updateObserverBox(info); 338 } 339 340 void handleClientPosition(ubyte[] packetData, ClientId clientId) 341 { 342 if (isSpawned(clientId)) 343 { 344 auto packet = unpackPacket!ClientPositionPacket(packetData); 345 ClientInfo* info = clients[clientId]; 346 347 // reject stale position. Dimention already have changed. 348 if (packet.positionKey != info.positionKey) 349 return; 350 351 info.pos = vec3(packet.pos); 352 info.heading = vec2(packet.heading); 353 updateObserverBox(info); 354 } 355 } 356 }