1 /*
2  * Copyright (c) 2017-2018 SEL
3  * 
4  * This program is free software: you can redistribute it and/or modify
5  * it under the terms of the GNU Lesser General Public License as published by
6  * the Free Software Foundation, either version 3 of the License, or
7  * (at your option) any later version.
8  * 
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
12  * See the GNU Lesser General Public License for more details.
13  * 
14  */
15 /**
16  * Copyright: 2017-2020 sel-project
17  * License: LGPL-3.0
18  * Authors: Kripth
19  * Source: $(HTTP github.com/sel-project/sel-server/sel/server/server.d, sel/server/server.d)
20  */
21 module sel.server.server;
22 
23 import core.atomic : atomicOp;
24 
25 import std.algorithm : sort, all, canFind;
26 import std.conv : to;
27 import std.socket : Address, getAddress;
28 import std.typetuple : TypeTuple;
29 
30 import sel.server.client : Client;
31 import sel.server.query : Query;
32 
33 /**
34  * Basic server's informations, used to display the server in the
35  * game's server list and in the queries.
36  */
37 class ServerInfo {
38 
39 	static struct MOTD {
40 
41 		string raw;
42 
43 		string bedrock, java;
44 
45 		this(string motd) {
46 			this.opAssign(motd);
47 		}
48 
49 		void opAssign(string motd) {
50 			this.raw = motd; //TODO remove formatting
51 			this.bedrock = this.java = motd;
52 		}
53 
54 		shared void opAssign(string motd) {
55 			(cast()this).opAssign(motd);
56 		}
57 
58 	}
59 	
60 	public MOTD motd = MOTD("A Minecraft Server");
61 	
62 	public int online = 0;
63 	public int max = 32;
64 
65 	public string favicon; // must be already encoded
66 
67 	public string gametype = "SMP";
68 	public string map = "world";
69 	
70 }
71 
72 abstract class GenericServer {
73 
74 	protected shared ServerInfo info;
75 
76 	public shared this(shared ServerInfo info) {
77 		this.info = info;
78 	}
79 	
80 	/**
81 	 * Starts the server on the given address.
82 	 * The server can be started using an ip/port combination (if the port
83 	 * is not given the game's default one will be used), and an optional
84 	 * handler (for the management of the players) and a query.
85 	 */
86 	public final shared void start(Address address, shared Query query=null) {
87 		this.startImpl(address, query);
88 	}
89 	
90 	/// ditto
91 	public final shared void start(string ip, ushort port, shared Query query=null) {
92 		this.start(getAddress(ip, port)[0], query);
93 	}
94 	
95 	/// ditto
96 	public final shared void start(string ip, shared Query query=null) {
97 		this.start(ip, this.defaultPort, query);
98 	}
99 	
100 	protected abstract shared void startImpl(Address address, shared Query query);
101 	
102 	/**
103 	 * Gets the server's default port for the hosted game.
104 	 */
105 	public abstract shared pure nothrow @property @safe @nogc ushort defaultPort();
106 
107 }
108 
109 /**
110  * A generic server that only contains a ServerInfo, the supported protocols
111  * and the mothods to start it.
112  */
113 abstract class GenericGameServer : GenericServer {
114 
115 	private immutable immutable(uint)[] supported;
116 	protected immutable(uint)[] _protocols;
117 	protected shared Handler handler;
118 	
119 	public shared this(shared ServerInfo info, uint[] protocols, uint[] supported, shared Handler handler) {
120 		super(info);
121 		this.supported = supported.idup;
122 		this.protocols = protocols;
123 		this.handler = handler;
124 	}
125 
126 	public final shared pure nothrow @property @safe @nogc immutable(uint)[] protocols() {
127 		return this._protocols;
128 	}
129 
130 	public final shared @property immutable(uint)[] protocols(uint[] protocols) {
131 		return this._protocols = checkProtocols(protocols, this.supported).idup;
132 	}
133 
134 	protected shared void onClientJoin(shared Client client) {
135 		atomicOp!"+="(this.info.online, 1);
136 		this.handler.onClientJoin(client);
137 	}
138 
139 	protected shared void onClientLeft(shared Client client) {
140 		atomicOp!"-="(this.info.online, 1);
141 		this.handler.onClientLeft(client);
142 	}
143 
144 }
145 
146 class Handler {
147 
148 	public shared void onClientJoin(shared Client client) {}
149 
150 	public shared void onClientLeft(shared Client client) {}
151 
152 	public shared void onClientPacket(shared Client client, ubyte[] packet) {}
153 
154 }
155 
156 uint[] checkProtocols(uint[] protocols, inout(uint)[] supported) {
157 	sort(protocols);
158 	uint[] ret;
159 	foreach(i, protocol; protocols) {
160 		if(supported.canFind(protocol) && (ret.length == 0 || protocol != ret[$-1])) {
161 			ret ~= protocol;
162 		}
163 	}
164 	return ret;
165 }
166 
167 template TupleOf(alias array) {
168 	mixin((){
169 		string ret = "alias TupleOf = TypeTuple!(";
170 		foreach(element ; array) {
171 			ret ~= element.to!string;
172 			ret ~= ",";
173 		}
174 		return ret ~ ");";
175 	}());
176 }