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 }