package tc.oc.pgm.flag; import java.util.ArrayList; import java.util.List; import java.util.Optional; import java.util.logging.Logger; import java.util.stream.Stream; import javax.annotation.Nullable; import javax.inject.Inject; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import net.md_5.bungee.api.chat.BaseComponent; import org.bukkit.DyeColor; import org.bukkit.util.Vector; import org.jdom2.Document; import org.jdom2.Element; import java.time.Duration; import tc.oc.commons.core.stream.Collectors; import tc.oc.pgm.features.FeatureParser; import tc.oc.pgm.filters.Filter; import tc.oc.pgm.filters.matcher.StaticFilter; import tc.oc.pgm.filters.parser.FilterParser; import tc.oc.pgm.goals.ProximityMetric; import tc.oc.pgm.kits.Kit; import tc.oc.pgm.kits.KitParser; import tc.oc.pgm.kits.RemovableValidation; import tc.oc.pgm.map.MapModuleContext; import tc.oc.pgm.map.MapRootParser; import tc.oc.pgm.points.PointParser; import tc.oc.pgm.points.PointProvider; import tc.oc.pgm.points.PointProviderAttributes; import tc.oc.pgm.regions.Region; import tc.oc.pgm.regions.RegionParser; import tc.oc.pgm.teams.TeamFactory; import tc.oc.pgm.utils.XMLUtils; import tc.oc.pgm.xml.InvalidXMLException; import tc.oc.pgm.xml.Node; import static tc.oc.commons.core.exception.LambdaExceptionUtils.rethrowFunction; public class FlagParser implements MapRootParser { private final Document document; private final MapModuleContext context; private final PointParser pointParser; private final Logger logger; private final FilterParser filterParser; private final RegionParser regionParser; private final KitParser kitParser; private final FeatureParser<TeamFactory> teamParser; private final List<FlagDefinition> flags = new ArrayList<>(); @Inject private FlagParser(Document document, MapModuleContext context, PointParser pointParser, Logger logger, FeatureParser<TeamFactory> teamParser) { this.document = document; this.context = context; this.pointParser = pointParser; this.logger = logger; this.filterParser = context.needModule(FilterParser.class); this.regionParser = context.needModule(RegionParser.class); this.kitParser = context.needModule(KitParser.class); this.teamParser = teamParser; } private void checkDeprecatedFilter(Element el) throws InvalidXMLException { Node node = Node.fromChildOrAttr(el, "filter"); if(node != null) { throw new InvalidXMLException("'filter' is no longer supported, be more specific e.g. 'pickup-filter'", node); } } public Post parsePost(Element el) throws InvalidXMLException { checkDeprecatedFilter(el); final Optional<TeamFactory> owner = teamParser.property(el, "owner").optional(); boolean sequential = XMLUtils.parseBoolean(el.getAttribute("sequential"), false); boolean permanent = XMLUtils.parseBoolean(el.getAttribute("permanent"), false); double pointsPerSecond = XMLUtils.parseNumber(el.getAttribute("points-rate"), Double.class, 0D); Filter pickupFilter = filterParser.property(el, "pickup-filter").optional(StaticFilter.ALLOW); Duration recoverTime = XMLUtils.parseDuration(Node.fromAttr(el, "recover-time", "return-time"), Post.DEFAULT_RETURN_TIME); Duration respawnTime = XMLUtils.parseDuration(el.getAttribute("respawn-time"), null); Double respawnSpeed = XMLUtils.parseNumber(el.getAttribute("respawn-speed"), Double.class, (Double) null); ImmutableList<PointProvider> returnPoints = ImmutableList.copyOf(pointParser.parse(el, new PointProviderAttributes())); if(respawnTime == null && respawnSpeed == null) { respawnSpeed = Post.DEFAULT_RESPAWN_SPEED; } if(respawnTime != null && respawnSpeed != null) { throw new InvalidXMLException("post cannot have both respawn-time and respawn-speed", el); } if(returnPoints.isEmpty()) { throw new InvalidXMLException("post must have at least one point provider", el); } return context.features().define(el, Post.class, new PostImpl(owner, recoverTime, respawnTime, respawnSpeed, returnPoints, sequential, permanent, pointsPerSecond, pickupFilter)); } public ImmutableSet<FlagDefinition> parseFlagSet(Node node) throws InvalidXMLException { return Stream.of(node.getValue().split("\\s")) .map(rethrowFunction(flagId -> context.features().reference(node, flagId, FlagDefinition.class))) .collect(Collectors.toImmutableSet()); } public Net parseNet(Element el, @Nullable FlagDefinition parentFlag) throws InvalidXMLException { checkDeprecatedFilter(el); Region region = regionParser.property(el).union(); final Optional<TeamFactory> owner = teamParser.property(el, "owner").optional(); double pointsPerCapture = XMLUtils.parseNumber(el.getAttribute("points"), Double.class, 0D); boolean sticky = XMLUtils.parseBoolean(el.getAttribute("sticky"), true); Filter captureFilter = filterParser.property(el, "capture-filter").optional(StaticFilter.ALLOW); Filter respawnFilter = filterParser.property(el, "respawn-filter").optional(StaticFilter.ALLOW); boolean respawnTogether = XMLUtils.parseBoolean(el.getAttribute("respawn-together"), false); BaseComponent respawnMessage = XMLUtils.parseFormattedText(el, "respawn-message"); BaseComponent denyMessage = XMLUtils.parseFormattedText(el, "deny-message"); Vector proximityLocation = XMLUtils.parseVector(el.getAttribute("location"), (Vector) null); Post returnPost = null; Node postAttr = Node.fromAttr(el, "post"); if(postAttr != null) { // Posts are all parsed at this point, so we can do an immediate lookup returnPost = context.features().reference(postAttr, Post.class); if(returnPost == null) { throw new InvalidXMLException("No post with ID '" + postAttr.getValue() + "'", postAttr); } } ImmutableSet<FlagDefinition> capturableFlags; Node flagsAttr = Node.fromAttr(el, "flag", "flags"); if(flagsAttr != null) { if(parentFlag != null) { throw new InvalidXMLException("Cannot specify flags on a net that is defined inside a flag", flagsAttr); } capturableFlags = this.parseFlagSet(flagsAttr); } else if(parentFlag != null) { capturableFlags = ImmutableSet.of(parentFlag); } else { capturableFlags = ImmutableSet.copyOf(this.flags); } ImmutableSet<FlagDefinition> returnableFlags; Node returnableNode = Node.fromAttr(el, "rescue", "return"); if(returnableNode != null) { returnableFlags = this.parseFlagSet(returnableNode); } else { returnableFlags = ImmutableSet.of(); } return context.features().define(el, Net.class, new NetImpl(region, captureFilter, respawnFilter, owner, pointsPerCapture, sticky, denyMessage, respawnMessage, returnPost, capturableFlags, returnableFlags, respawnTogether, proximityLocation)); } public FlagDefinition parseFlag(Element el) throws InvalidXMLException { checkDeprecatedFilter(el); String name = el.getAttributeValue("name"); boolean visible = XMLUtils.parseBoolean(el.getAttribute("show"), true); Boolean required = XMLUtils.parseBoolean(el.getAttribute("required"), null); DyeColor color = XMLUtils.parseDyeColor(el.getAttribute("color"), null); final Optional<TeamFactory> owner = teamParser.property(el, "owner").optional(); double pointsPerCapture = XMLUtils.parseNumber(el.getAttribute("points"), Double.class, 0D); double pointsPerSecond = XMLUtils.parseNumber(el.getAttribute("points-rate"), Double.class, 0D); Filter pickupFilter = filterParser.property(el, "pickup-filter").optional(null); if(pickupFilter == null) pickupFilter = filterParser.property(el, "filter").optional(StaticFilter.ALLOW); Filter dropFilter = filterParser.property(el, "drop-filter").optional(StaticFilter.ALLOW); Filter captureFilter = filterParser.property(el, "capture-filter").optional(StaticFilter.ALLOW); Kit pickupKit = kitParser.property(el, "pickup-kit").optional(null); Kit dropKit = kitParser.property(el, "drop-kit").optional(null); Kit carryKit = kitParser.property(el, "carry-kit") .validate(RemovableValidation.get()) .optional(null); boolean multiCarrier = XMLUtils.parseBoolean(el.getAttribute("shared"), false); BaseComponent carryMessage = XMLUtils.parseFormattedText(el, "carry-message"); boolean dropOnWater = XMLUtils.parseBoolean(el.getAttribute("drop-on-water"), true); boolean showBeam = XMLUtils.parseBoolean(el.getAttribute("beam"), true); ProximityMetric flagProximityMetric = ProximityMetric.parse(el, "flag", new ProximityMetric(ProximityMetric.Type.CLOSEST_KILL, false)); ProximityMetric netProximityMetric = ProximityMetric.parse(el, "net", new ProximityMetric(ProximityMetric.Type.CLOSEST_PLAYER, false)); Post defaultPost; Element elPost = XMLUtils.getUniqueChild(el, "post"); if(elPost != null) { // Parse nested <post> defaultPost = this.parsePost(elPost); } else { Node postAttr = Node.fromRequiredAttr(el, "post"); defaultPost = context.features().reference(postAttr, Post.class); if(defaultPost == null) { throw new InvalidXMLException("No post with ID '" + postAttr.getValue() + "'", postAttr); } } FlagDefinition flag = context.features().define(el, FlagDefinition.class, new FlagDefinitionImpl(name, required, visible, color, defaultPost, owner, pointsPerCapture, pointsPerSecond, pickupFilter, dropFilter, captureFilter, pickupKit, dropKit, carryKit, multiCarrier, carryMessage, dropOnWater, showBeam, flagProximityMetric, netProximityMetric)); flags.add(flag); // Parse nested <net>s for(Element elNet : el.getChildren("net")) { this.parseNet(elNet, flag); } return flag; } @Override public void parse() throws InvalidXMLException { // Order of these is important to avoid the need for forward refs for(Element el : XMLUtils.flattenElements(document.getRootElement(), "flags", "post")) { this.parsePost(el); } for(Element el : XMLUtils.flattenElements(document.getRootElement(), "flags", "flag")) { this.parseFlag(el); } for(Element el : XMLUtils.flattenElements(document.getRootElement(), "flags", "net")) { this.parseNet(el, null); } } }