1 /*
2  * Copyright (c) 2017-2019 sel-project
3  *
4  * Permission is hereby granted, free of charge, to any person obtaining a copy
5  * of this software and associated documentation files (the "Software"), to deal
6  * in the Software without restriction, including without limitation the rights
7  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8  * copies of the Software, and to permit persons to whom the Software is
9  * furnished to do so, subject to the following conditions:
10  *
11  * The above copyright notice and this permission notice shall be included in all
12  * copies or substantial portions of the Software.
13  *
14  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20  * SOFTWARE.
21  *
22  */
23 /**
24  * Copyright: Copyright (c) 2017-2019 sel-project
25  * License: MIT
26  * Authors: Kripth
27  * Source: $(HTTP github.com/sel-project/selery/source/selery/command/util.d, selery/command/util.d)
28  */
29 module selery.command.util;
30 
31 import std.algorithm : sort;
32 import std.conv : to, ConvException;
33 import std.random : uniform;
34 import std..string : split, join, toLower, startsWith, replace;
35 import std.traits : isIntegral, isFloatingPoint;
36 import std.typecons : Tuple;
37 
38 import selery.command.command : Command;
39 import selery.config : Gamemode;
40 import selery.entity.entity : Entity;
41 import selery.log : Message;
42 import selery.math.vector : EntityPosition, isVector, distance;
43 import selery.node.server : NodeServer;
44 import selery.player.player : Player;
45 import selery.world.world : World;
46 
47 import transforms.snake;
48 
49 /**
50  * Interface for command senders.
51  */
52 interface CommandSender {
53 
54 	/**
55 	 * Gets the command sender's current server.
56 	 */
57 	public pure nothrow @property @safe @nogc shared(NodeServer) server();
58 
59 	/**
60 	 * Gets the commands that can be called by the command sender
61 	 * in its current status.
62 	 * Aliases are included in the list and the Command object is
63 	 * the same as the non-aliased command.
64 	 * Example:
65 	 * ---
66 	 * assert(sender.availableCommands["help"] is sender.availableCommands["?"]);
67 	 * ---
68 	 */
69 	public @property Command[string] availableCommands();
70 
71 	/**
72 	 * Sends a message to the command sender.
73 	 * The message can contain formatting codes and translations.
74 	 * Example:
75 	 * ---
76 	 * sender.sendMessage("Hello");
77 	 * sender.sendMessage(Format.blue, "This is a blue message");
78 	 * sender.sendMessage(Format.yellow, Translation("multiplayer.player.joined", "Steve"));
79 	 * ---
80 	 */
81 	public final void sendMessage(E...)(E args) {
82 		this.sendMessageImpl(Message.convert(args));
83 	}
84 
85 	protected void sendMessageImpl(Message[] messages);
86 	
87 }
88 
89 /**
90  * Interface for a command sender that is spawned in a world.
91  */
92 interface WorldCommandSender : CommandSender {
93 	
94 	/**
95 	 * Gets the command sender's world.
96 	 */
97 	public pure nothrow @property @safe @nogc World world();
98 	
99 	/**
100 	 * Gets the command sender's current position.
101 	 */
102 	public @property EntityPosition position();
103 	
104 	/**
105 	 * Gets the list of the entities visible by the
106 	 * command sender.
107 	 */
108 	public @property Entity[] visibleEntities();
109 	
110 	/**
111 	 * Gets the list of the players visible by the
112 	 * command sender.
113 	 */
114 	public @property Player[] visiblePlayers();
115 	
116 }
117 
118 enum PocketType {
119 	
120 	target,
121 	blockpos,
122 	stringenum,
123 	string,
124 	rawtext,
125 	integer,
126 	floating,
127 	boolean,
128 	
129 }
130 
131 /**
132  * Created an enum with a single value that can be used in
133  * commands with a single argument.
134  * Example:
135  * ---
136  * // test add @a
137  * @command("test") test0(SingleEnum!"add", Target target) {}
138  * 
139  * // test remove @a
140  * @command("test") test1(SingleEnum!"remove", Target target) {}
141  * ---
142  */
143 template SingleEnum(string value) {
144 	
145 	mixin("enum SingleEnum { " ~ value ~ " }");
146 	
147 }
148 
149 /**
150  * Example:
151  * ---
152  * enum Example : int {
153  *    plain = 12,
154  *    camelCase = 44,
155  *    PascalCase = 100,
156  *    ALL_UPPERCASE = 200
157  * }
158  * alias Snake = SnakeCaseEnum!Example;
159  * assert(Example.plain == Snake.plain);
160  * assert(Example.camelCase == Snake.camel_case);
161  * assert(Example.PascalCase == Snake.pascal_case);
162  * assert(Example.ALL_UPPERCASE == Snake.all_uppercase);
163  * ---
164  */
165 template SnakeCaseEnum(T) if(is(T == enum)) {
166 	
167 	mixin("enum SnakeCaseEnum {" ~ (){
168 		string ret;
169 		foreach(immutable member ; __traits(allMembers, T)) {
170 			ret ~= member.snakeCaseCT ~ "=T." ~ member ~ ",";
171 		}
172 		return ret;
173 	}() ~ "}");
174 	
175 }
176 
177 struct Ranged(T, string _type, T _min, T _max) if((isIntegral!T || isFloatingPoint!T) && _min < _max && (_type == "[]" || _type == "(]" || _type == "[)" || _type == "()")) {
178 
179 	enum __is_range;
180 
181 	alias Type = T;
182 
183 	enum type = _type;
184 
185 	enum min = _min;
186 	enum max = _max;
187 
188 	T value;
189 
190 	alias value this;
191 
192 }
193 
194 alias Ranged(T, T min, T max) = Ranged!(T, "[]", min, max);
195 
196 enum isRanged(T) = __traits(hasMember, T, "__is_range");
197 
198 template minImpl(T) {
199 	static if(isIntegral!T) enum minImpl = T.min;
200 	else enum minImpl = T.min_normal;
201 }
202 
203 /**
204  * Indicates a position with absolutes and/or relatives coordinates.
205  * Example:
206  * ---
207  * auto pos = Position(Position.Point.fromString("~"), Position.Point.fromString("1"), Position.Point.fromString("~10"));
208  * auto res = pos.from(BlockPosition(1, 10, 100));
209  * assert(res == BlockPosition(1, 1, 110));
210  * ---
211  */
212 struct PositionImpl(V) if(isVector!V) {
213 
214 	alias T = V.Type;
215 	
216 	static struct Point {
217 		
218 		private bool absolute;
219 		private T _value;
220 		private T function(T, T) _apply;
221 		
222 		public this(bool absolute, immutable T v) {
223 			this.absolute = absolute;
224 			this._value = v;
225 			if(absolute) {
226 				this._apply = &applyAbsolute;
227 			} else {
228 				this._apply = &applyRelative;
229 			}
230 		}
231 		
232 		private static T applyAbsolute(T a, T b) {
233 			return a;
234 		}
235 		
236 		private static T applyRelative(T a, T b) {
237 			return b + a;
238 		}
239 		
240 		public T apply(T value) {
241 			return this._apply(this._value, value);
242 		}
243 		
244 		public string toString() {
245 			if(this.absolute) {
246 				return to!string(this._value);
247 			} else if(this._value == 0) {
248 				return "~";
249 			} else {
250 				return "~" ~ to!string(this._value);		
251 			}
252 		}
253 		
254 		public static Point fromString(string str) {
255 			if(str.length) {
256 				if(str[0] == '~') {
257 					if(str.length == 1) {
258 						return Point(false, 0);
259 					} else {
260 						return Point(false, to!T(str[1..$]));
261 					}
262 				} else {
263 					return Point(true, to!T(str));
264 				}
265 			} else {
266 				return Point(true, T.init);
267 			}
268 		}
269 		
270 	}
271 
272 	public static typeof(this) fromString(string str) {
273 		auto spl = str.split(" ");
274 		if(spl.length != 3) throw new ConvException("Wrong format");
275 		else return typeof(this)(Point.fromString(spl[0]), Point.fromString(spl[1]), Point.fromString(spl[2]));
276 	}
277 	
278 	mixin((){
279 		string ret;
280 		foreach(c ; V.coords) {
281 			ret ~= "public Point " ~ c ~ ";";
282 		}
283 		return ret;
284 	}());
285 	
286 	/**
287 	 * Creates a vector from an initial position (used for
288 	 * relative values).
289 	 */
290 	public @property V from(V position) {
291 		T[V.coords.length] ret;
292 		foreach(i, c; V.coords) {
293 			mixin("ret[i] = this." ~ c ~ ".apply(position." ~ c ~ ");");
294 		}
295 		return V(ret);
296 	}
297 	
298 	public string toCoordsString(string glue=", ") {
299 		string[] ret;
300 		foreach(c ; V.coords) {
301 			ret ~= mixin("this." ~ c ~ ".toString()");
302 		}
303 		return ret.join(glue);
304 	}
305 
306 	public string toString() {
307 		return "Position(" ~ this.toCoordsString() ~ ")";
308 	}
309 	
310 }
311 
312 /// ditto
313 alias Position = PositionImpl!EntityPosition;
314 
315 /**
316  * Indicates a target selected using a username or a target selector.
317  * For reference see $(LINK2 https://minecraft.gamepedia.com/Commands#Target_selector_variables, Command on Minecraft Wiki).
318  */
319 struct Target {
320 
321 	/**
322 	 * Raw input of the selector used.
323 	 */
324 	public string input;
325 	
326 	public Entity[] entities;
327 	public Player[] players;
328 
329 	/**
330 	 * Indicates whether the target was a player or an entity.
331 	 * Example:
332 	 * ---
333 	 * "Steve" = true
334 	 * "@a" = true
335 	 * "@e" = false
336 	 * "@e[type=player]" = true
337 	 * "@r" = true
338 	 * "@r[type=creeper]" = false
339 	 * ---
340 	 */
341 	public bool player = true;
342 
343 	public this(string input) {
344 		this.input = input;
345 	}
346 	
347 	public this(string input, Entity[] entities, bool player=true) {
348 		this(input);
349 		this.entities = entities;
350 		foreach(entity ; entities) {
351 			if(cast(Player)entity) this.players ~= cast(Player)entity;
352 		}
353 		this.player = player;
354 	}
355 	
356 	public this(string input, Player[] players, bool player=true) {
357 		this(input);
358 		this.entities = cast(Entity[])players;
359 		this.players = players;
360 	}
361 	
362 	/**
363 	 * Creates a target from a username or a selector string.
364 	 */
365 	public static Target fromString(WorldCommandSender sender, string str) {
366 		if(str.length >= 2 && str[0] == '@') {
367 			string[string] selectors;
368 			if(str.length >= 4 && str[2] == '[' && str[$-1] == ']') {
369 				foreach(sel ; str[3..$-1].split(",")) {
370 					auto spl = sel.split("=");
371 					if(spl.length == 2) selectors[spl[0]] = spl[1];
372 				}
373 			}
374 			switch(str[1]) {
375 				case 's':
376 					if(cast(Entity)sender) {
377 						return Target(str, [cast(Entity)sender]);
378 					} else {
379 						return Target(str);
380 					}
381 				case 'p':
382 					auto players = sender.visiblePlayers;
383 					if(players.length) {
384 						if("c" !in selectors) {
385 							selectors["c"] = "1";
386 						}
387 						filter(sender, players, selectors);
388 						//TODO sort per distance
389 						return Target(str);
390 					} else {
391 						return Target(str);
392 					}
393 				case 'r':
394 					size_t amount = 1;
395 					auto c = "c" in selectors;
396 					if(c) {
397 						try {
398 							amount = to!size_t(*c);
399 						} catch(ConvException) {}
400 						selectors.remove("c");
401 					}
402 					Target rImpl(T:Entity)(T[] data) {
403 						filter(sender, data, selectors);
404 						if(amount >= data.length) {
405 							return Target(str, data);
406 						} else {
407 							T[] selected;
408 							while(--amount) {
409 								size_t index = uniform(0, data.length);
410 								selected ~= data[index];
411 								data = data[0..index] ~ data[index+1..$];
412 							}
413 							return Target(str, selected, is(T == Player));
414 						}
415 					}
416 					auto type = "type" in selectors;
417 					if(type && *type != "player") {
418 						return rImpl(sender.visibleEntities);
419 					} else {
420 						return rImpl(sender.visiblePlayers);
421 					}
422 				case 'a':
423 					auto players = sender.visiblePlayers;
424 					filter(sender, players, selectors);
425 					return Target(str, players, true);
426 				case 'e':
427 					auto entities = sender.visibleEntities;
428 					filter(sender, entities, selectors);
429 					return Target(str, entities, false);
430 				default:
431 					return Target(str);
432 			}
433 		} else {
434 			immutable sel = str.toLower;
435 			Player[] ret;
436 			foreach(player ; sender.visiblePlayers) {
437 				if(player.lname == sel) ret ~= player;
438 			}
439 			return Target(str, ret);
440 		}
441 	}
442 	
443 }
444 
445 private struct Res {
446 	
447 	bool exists;
448 	bool inverted;
449 	string value;
450 	
451 	alias exists this;
452 	
453 }
454 
455 private void filter(T:Entity)(WorldCommandSender sender, ref T[] entities, string[string] selectors) {
456 	Res data(string key) {
457 		auto p = key in selectors;
458 		if(p) {
459 			if((*p).startsWith("!")) return Res(true, true, (*p)[1..$]);
460 			else return Res(true, false, *p);
461 		} else {
462 			return Res(false);
463 		}
464 	}
465 	auto type = data("type");
466 	if(type) {
467 		// filter type
468 		if(!type.inverted) filterImpl!("entity.type == a")(entities, type.value);
469 		else filterImpl!("entity.type != a")(entities, type.value);
470 	}
471 	auto name = data("name");
472 	if(name) {
473 		// filter by nametag
474 		if(!name.inverted) filterImpl!("entity.nametag == a")(entities, name.value);
475 		else filterImpl!("entity.nametag != a")(entities, name.value);
476 	}
477 	auto rx = data("rx");
478 	if(rx) {
479 		// filter by max pitch
480 		try { filterImpl!("entity.pitch <= a")(entities, to!float(name.value)); } catch(ConvException) {}
481 	}
482 	auto rxm = data("rxm");
483 	if(rxm) {
484 		// filter by min pitch
485 		try { filterImpl!("entity.pitch >= a")(entities, to!float(name.value)); } catch(ConvException) {}
486 	}
487 	auto ry = data("ry");
488 	if(ry) {
489 		// filter by max yaw
490 		try { filterImpl!("entity.yaw <= a")(entities, to!float(name.value)); } catch(ConvException) {}
491 	}
492 	auto rym = data("rym");
493 	if(rym) {
494 		// filter by min yaw
495 		try { filterImpl!("entity.yaw >= a")(entities, to!float(name.value)); } catch(ConvException) {}
496 	}
497 	auto m = data("m");
498 	auto l = data("l");
499 	auto lm = data("lm");
500 	if(m || l || lm) {
501 		static if(is(T : Player)) {
502 			alias players = entities;
503 		} else {
504 			// filter out non-players
505 			Player[] players;
506 			foreach(entity ; entities) {
507 				auto player = cast(Player)entity;
508 				if(player !is null) players ~= player;
509 			}
510 		}
511 		if(m) {
512 			// filter gamemode
513 			int gamemode = (){
514 				switch(m.value) {
515 					case "0": case "s": case "survival": return 0;
516 					case "1": case "c": case "creative": return 1;
517 					case "2": case "a": case "adventure": return 2;
518 					case "3": case "sp": case "spectator": return 3;
519 					default: return -1;
520 				}
521 			}();
522 			if(gamemode >= 0) {
523 				if(!m.inverted) filterImpl!("entity.gamemode == a")(players, gamemode);
524 				else filterImpl!("entity.gamemode != a")(players, gamemode);
525 			}
526 		}
527 		if(l) {
528 			// filter xp (min)
529 			try {
530 				filterImpl!("entity.level <= a")(players, to!uint(l.value));
531 			} catch(ConvException) {}
532 		}
533 		if(lm) {
534 			// filter xp (max)
535 			try {
536 				filterImpl!("entity.level >= a")(players, to!uint(l.value));
537 			} catch(ConvException) {}
538 		}
539 		static if(!is(T : Player)) {
540 			entities = cast(Entity[])players;
541 		}
542 	}
543 	auto c = data("c");
544 	if(c) {
545 		try {
546 			auto amount = to!ptrdiff_t(c.value);
547 			if(amount > 0) {
548 				entities = filterDistance!false(sender.position, entities, amount);
549 			} else if(amount < 0) {
550 				entities = filterDistance!true(sender.position, entities, -amount);
551 			} else {
552 				entities.length = 0;
553 			}
554 		} catch(ConvException) {}
555 	}
556 }
557 
558 private void filterImpl(string query, T:Entity, A)(ref T[] entities, A a) {
559 	T[] ret;
560 	foreach(entity ; entities) {
561 		if(mixin(query)) ret ~= entity;
562 	}
563 	if(ret.length != entities.length) entities = ret;
564 }
565 
566 private T[] filterDistance(bool inverted, T:Entity)(EntityPosition position, T[] entities, size_t count) {
567 	if(count >= entities.length) return entities;
568 	Tuple!(T, double)[] distances;
569 	foreach(entity ; entities) {
570 		distances ~= Tuple!(T, double)(entity, distance(position, entity.position));
571 	}
572 	sort!((a, b) => a[1] == b[1] ? a[0].id < b[0].id : a[1] < b[1])(distances);
573 	T[] ret;
574 	foreach(i ; 0..count) {
575 		static if(inverted) ret ~= distances[$-i-1][0];
576 		else ret ~= distances[i][0];
577 	}
578 	return ret;
579 }