package com.laytonsmith.core.events.drivers; import com.laytonsmith.PureUtilities.Version; import com.laytonsmith.abstraction.MCLocation; import com.laytonsmith.abstraction.MCPlayer; import com.laytonsmith.abstraction.events.MCCommandTabCompleteEvent; import com.laytonsmith.abstraction.events.MCRedstoneChangedEvent; import com.laytonsmith.abstraction.events.MCServerPingEvent; import com.laytonsmith.annotations.api; import com.laytonsmith.core.ArgumentValidation; import com.laytonsmith.core.CHVersion; import com.laytonsmith.core.ObjectGenerator; import com.laytonsmith.core.Static; import com.laytonsmith.core.constructs.CArray; import com.laytonsmith.core.constructs.CBoolean; import com.laytonsmith.core.constructs.CInt; import com.laytonsmith.core.constructs.CString; import com.laytonsmith.core.constructs.Construct; import com.laytonsmith.core.constructs.Target; import com.laytonsmith.core.events.AbstractEvent; import com.laytonsmith.core.events.BindableEvent; import com.laytonsmith.core.events.Driver; import com.laytonsmith.core.events.Prefilters; import com.laytonsmith.core.events.Prefilters.PrefilterType; import com.laytonsmith.core.exceptions.ConfigRuntimeException; import com.laytonsmith.core.exceptions.EventException; import com.laytonsmith.core.exceptions.PrefilterNonMatchException; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; /** * * */ public class ServerEvents { @api public static class server_ping extends AbstractEvent { @Override public String getName() { return "server_ping"; } @Override public String docs() { return "{players: <math match> | maxplayers: <math match>}" + " Fired when a user who has saved this server looks at their serverlist." + " {ip: The address the ping is coming from | players: The number of players online" + " | maxplayers: The number of slots on the server | motd: The message a player is shown on the serverlist" + " | list: The list of connected players}" + " {motd | maxplayers | list: It is only possible to remove players, the added players" + " will be ignored. This will also change the player count.}" + " {}"; } @Override public boolean matches(Map<String, Construct> prefilter, BindableEvent e) throws PrefilterNonMatchException { if (e instanceof MCServerPingEvent) { MCServerPingEvent event = (MCServerPingEvent) e; Prefilters.match(prefilter, "players", event.getNumPlayers(), PrefilterType.MATH_MATCH); Prefilters.match(prefilter, "maxplayers", event.getMaxPlayers(), PrefilterType.MATH_MATCH); return true; } return false; } @Override public BindableEvent convert(CArray manualObject, Target t) { throw ConfigRuntimeException.CreateUncatchableException("Unsupported Operation", Target.UNKNOWN); } @Override public Map<String, Construct> evaluate(BindableEvent e) throws EventException { if (e instanceof MCServerPingEvent) { MCServerPingEvent event = (MCServerPingEvent) e; Target t = Target.UNKNOWN; Map<String, Construct> ret = evaluate_helper(event); String ip; try { ip = event.getAddress().getHostAddress(); } catch (NullPointerException npe) { ip = ""; } ret.put("ip", new CString(ip, t)); ret.put("motd", new CString(event.getMOTD(), t)); ret.put("players", new CInt(event.getNumPlayers(), t)); ret.put("maxplayers", new CInt(event.getMaxPlayers(), t)); CArray players = new CArray(t); for (MCPlayer player : event.getPlayers()) { players.push(new CString(player.getName(), t), t); } ret.put("list", players); return ret; } else { throw new EventException("Could not convert to MCPingEvent"); } } @Override public boolean modifyEvent(String key, Construct value, BindableEvent event) { if (event instanceof MCServerPingEvent) { MCServerPingEvent e = (MCServerPingEvent) event; switch (key.toLowerCase()) { case "motd": e.setMOTD(value.val()); return true; case "maxplayers": e.setMaxPlayers(Static.getInt32(value, Target.UNKNOWN)); return true; case "list": // Modifies the player list. The new list will be the intersection of the original // and the given list. Names and UUID's outside this intersection will simply be ignored. Set<MCPlayer> modifiedPlayers = new HashSet<>(); List<Construct> passedList = ArgumentValidation.getArray(value, Target.UNKNOWN).asList(); for(MCPlayer player : e.getPlayers()) { for(Construct construct : passedList) { String playerStr = construct.val(); if(playerStr.length() > 0 && playerStr.length() <= 16) { // "player" is a name. if(playerStr.equalsIgnoreCase(player.getName())) { modifiedPlayers.add(player); break; } } else { // "player" is the UUID of the player. if(playerStr.equalsIgnoreCase(player.getUniqueID().toString())) { modifiedPlayers.add(player); break; } } } } e.setPlayers(modifiedPlayers); return true; } } return false; } @Override public Driver driver() { return Driver.SERVER_PING; } @Override public Version since() { return CHVersion.V3_3_1; } } @api public static class tab_complete_command extends AbstractEvent { @Override public String getName() { return "tab_complete_command"; } @Override public String docs() { return "{}" + " This will fire if a tab completer has not been set for a command registered with" + " register_command(), or if the set tab completer doesn't return an array. If completions are " + " not modified, registered commands will tab complete online player names." + " {command: The command name that was registered. | alias: The alias the player entered to run" + " the command. | args: The given arguments after the alias. | completions: The available" + " completions for the last argument. | sender: The player that ran the command. }" + " {completions}" + " {}"; } @Override public boolean matches(Map<String, Construct> prefilter, BindableEvent event) throws PrefilterNonMatchException { return event instanceof MCCommandTabCompleteEvent; } @Override public BindableEvent convert(CArray manualObject, Target t) { throw ConfigRuntimeException.CreateUncatchableException("Unsupported Operation", Target.UNKNOWN); } @Override public Map<String, Construct> evaluate(BindableEvent event) throws EventException { if (event instanceof MCCommandTabCompleteEvent) { MCCommandTabCompleteEvent e = (MCCommandTabCompleteEvent) event; Target t = Target.UNKNOWN; Map<String, Construct> ret = evaluate_helper(event); ret.put("sender", new CString(e.getCommandSender().getName(), t)); CArray comp = new CArray(t); if(e.getCompletions() != null){ for (String c : e.getCompletions()) { comp.push(new CString(c, t), t); } } ret.put("completions", comp); ret.put("command", new CString(e.getCommand().getName(), t)); CArray args = new CArray(t); for (String a : e.getArguments()) { args.push(new CString(a, t), t); } ret.put("args", args); ret.put("alias", new CString(e.getAlias(), t)); return ret; } else { throw new EventException("Could not convert to MCCommandTabCompleteEvent"); } } @Override public Driver driver() { return Driver.TAB_COMPLETE; } @Override public boolean modifyEvent(String key, Construct value, BindableEvent event) { if (event instanceof MCCommandTabCompleteEvent) { MCCommandTabCompleteEvent e = (MCCommandTabCompleteEvent) event; if ("completions".equals(key)) { if (value instanceof CArray) { List<String> comp = new ArrayList<>(); if (((CArray) value).inAssociativeMode()) { for (Construct k : ((CArray) value).keySet()) { comp.add(((CArray) value).get(k, Target.UNKNOWN).val()); } } else { for (Construct v : ((CArray) value).asList()) { comp.add(v.val()); } } e.setCompletions(comp); return true; } } } return false; } @Override public Version since() { return CHVersion.V3_3_1; } } private final static Map<MCLocation, Boolean> redstoneMonitors = Collections.synchronizedMap(new HashMap<MCLocation, Boolean>()); /** * Returns a synchronized set of redstone monitors. When iterating on the * list, be sure to synchronize manually. * @return */ public static Map<MCLocation, Boolean> getRedstoneMonitors(){ return redstoneMonitors; } @api public static class redstone_changed extends AbstractEvent { @Override public void hook() { redstoneMonitors.clear(); } @Override public String getName() { return "redstone_changed"; } @Override public String docs() { return "{location: <location match>}" + " Fired when a redstone activatable block is toggled, either on or off, AND the block has been set to be monitored" + " with the monitor_redstone function." + " {location: The location of the block | active: Whether or not the block is now active, or disabled.}" + " {}" + " {}"; } @Override public boolean matches(Map<String, Construct> prefilter, BindableEvent e) throws PrefilterNonMatchException { if(e instanceof MCRedstoneChangedEvent){ MCRedstoneChangedEvent event = (MCRedstoneChangedEvent) e; Prefilters.match(prefilter, "location", event.getLocation(), PrefilterType.LOCATION_MATCH); return true; } return false; } @Override public BindableEvent convert(CArray manualObject, Target t) { throw new UnsupportedOperationException("Not supported yet."); } @Override public Map<String, Construct> evaluate(BindableEvent e) throws EventException { MCRedstoneChangedEvent event = (MCRedstoneChangedEvent) e; Map<String, Construct> map = evaluate_helper(e); map.put("location", ObjectGenerator.GetGenerator().location(event.getLocation())); map.put("active", CBoolean.get(event.isActive())); return map; } @Override public Driver driver() { return Driver.REDSTONE_CHANGED; } @Override public boolean modifyEvent(String key, Construct value, BindableEvent event) { return false; } @Override public Version since() { return CHVersion.V3_3_1; } } }