|
3 | 3 | import static net.kyori.adventure.key.Key.key;
|
4 | 4 | import static net.kyori.adventure.sound.Sound.sound;
|
5 | 5 | import static net.kyori.adventure.text.Component.empty;
|
6 |
| -import static net.kyori.adventure.text.Component.text; |
7 | 6 |
|
8 | 7 | import com.google.common.collect.ImmutableList;
|
9 | 8 | import com.google.common.collect.ImmutableMap;
|
10 | 9 | import java.lang.reflect.Method;
|
11 |
| -import java.text.DecimalFormat; |
12 |
| -import java.text.NumberFormat; |
13 |
| -import java.util.ArrayList; |
14 | 10 | import java.util.List;
|
15 |
| -import java.util.Locale; |
16 | 11 | import java.util.Map;
|
17 | 12 | import java.util.Set;
|
18 | 13 | import net.kyori.adventure.sound.Sound;
|
|
37 | 32 | import tc.oc.pgm.action.actions.TakePaymentAction;
|
38 | 33 | import tc.oc.pgm.action.actions.TeleportAction;
|
39 | 34 | import tc.oc.pgm.action.actions.VelocityAction;
|
| 35 | +import tc.oc.pgm.action.replacements.Replacement; |
| 36 | +import tc.oc.pgm.action.replacements.ReplacementParser; |
40 | 37 | import tc.oc.pgm.api.feature.FeatureValidation;
|
41 | 38 | import tc.oc.pgm.api.filter.Filter;
|
42 | 39 | import tc.oc.pgm.api.filter.Filterables;
|
|
54 | 51 | import tc.oc.pgm.shops.ShopModule;
|
55 | 52 | import tc.oc.pgm.shops.menu.Payable;
|
56 | 53 | import tc.oc.pgm.structure.StructureDefinition;
|
57 |
| -import tc.oc.pgm.util.Audience; |
58 | 54 | import tc.oc.pgm.util.MethodParser;
|
59 | 55 | import tc.oc.pgm.util.MethodParsers;
|
60 | 56 | import tc.oc.pgm.util.inventory.ItemMatcher;
|
61 | 57 | import tc.oc.pgm.util.math.Formula;
|
62 |
| -import tc.oc.pgm.util.named.NameStyle; |
63 | 58 | import tc.oc.pgm.util.xml.InvalidXMLException;
|
64 | 59 | import tc.oc.pgm.util.xml.Node;
|
65 | 60 | import tc.oc.pgm.util.xml.XMLFluentParser;
|
|
68 | 63 |
|
69 | 64 | public class ActionParser {
|
70 | 65 |
|
71 |
| - private static final NumberFormat DEFAULT_FORMAT = NumberFormat.getIntegerInstance(); |
72 |
| - |
73 | 66 | private final MapFactory factory;
|
74 | 67 | private final boolean legacy;
|
75 | 68 | private final FeatureDefinitionContext features;
|
76 | 69 | private final XMLFluentParser parser;
|
77 | 70 | private final Map<String, Method> methodParsers;
|
| 71 | + private final ReplacementParser replacementParser; |
78 | 72 |
|
79 | 73 | public ActionParser(MapFactory factory) {
|
80 | 74 | this.factory = factory;
|
81 | 75 | this.legacy = !factory.getProto().isNoOlderThan(MapProtos.ACTION_REVAMP);
|
82 | 76 | this.features = factory.getFeatures();
|
83 | 77 | this.parser = factory.getParser();
|
84 | 78 | this.methodParsers = MethodParsers.getMethodParsersForClass(getClass());
|
| 79 | + replacementParser = new ReplacementParser(factory); |
85 | 80 | }
|
86 | 81 |
|
87 | 82 | public <B extends Filterable<?>> Action<? super B> parseProperty(
|
@@ -290,82 +285,19 @@ public <T extends Filterable<?>> MessageAction<?> parseChatMessage(Element el, C
|
290 | 285 |
|
291 | 286 | List<Element> replacements = XMLUtils.flattenElements(el, "replacements");
|
292 | 287 | if (replacements.isEmpty()) {
|
293 |
| - return new MessageAction<>(Audience.class, text, actionbar, title, null); |
| 288 | + return new MessageAction<>(Filterable.class, text, actionbar, title, null); |
294 | 289 | }
|
295 | 290 |
|
296 | 291 | scope = parseScope(el, scope);
|
297 | 292 |
|
298 |
| - ImmutableMap.Builder<String, MessageAction.Replacement<T>> replacementMap = |
299 |
| - ImmutableMap.builder(); |
| 293 | + ImmutableMap.Builder<String, Replacement> replacementMap = ImmutableMap.builder(); |
300 | 294 | for (Element replacement : XMLUtils.flattenElements(el, "replacements")) {
|
301 | 295 | replacementMap.put(
|
302 |
| - XMLUtils.parseRequiredId(replacement), parseReplacement(replacement, scope)); |
| 296 | + XMLUtils.parseRequiredId(replacement), replacementParser.parse(replacement, scope)); |
303 | 297 | }
|
304 | 298 | return new MessageAction<>(scope, text, actionbar, title, replacementMap.build());
|
305 | 299 | }
|
306 | 300 |
|
307 |
| - private <T extends Filterable<?>> MessageAction.Replacement<T> parseReplacement( |
308 |
| - Element el, Class<T> scope) throws InvalidXMLException { |
309 |
| - // TODO: Support alternative replacement types (eg: player(s), team(s), or durations) |
310 |
| - switch (el.getName().toLowerCase(Locale.ROOT)) { |
311 |
| - case "decimal": { |
312 |
| - Formula<T> formula = parser.formula(scope, el, "value").required(); |
313 |
| - Node formatNode = Node.fromAttr(el, "format"); |
314 |
| - NumberFormat format = |
315 |
| - formatNode != null ? new DecimalFormat(formatNode.getValue()) : DEFAULT_FORMAT; |
316 |
| - return (T filterable) -> text(format.format(formula.applyAsDouble(filterable))); |
317 |
| - } |
318 |
| - case "player": { |
319 |
| - var variable = parser.variable(el, "var").scope(MatchPlayer.class).singleExclusive(); |
320 |
| - var fallback = XMLUtils.parseFormattedText(el, "fallback", empty()); |
321 |
| - var nameStyle = parser.parseEnum(NameStyle.class, el, "style").optional(NameStyle.VERBOSE); |
322 |
| - |
323 |
| - return (T filterable) -> |
324 |
| - variable.getHolder(filterable).map(mp -> mp.getName(nameStyle)).orElse(fallback); |
325 |
| - } |
326 |
| - case "switch": { |
327 |
| - Formula<T> formula = parser.formula(scope, el, "value").orNull(); |
328 |
| - var fallback = parser.formattedText(el, "fallback").child().optional(empty()); |
329 |
| - var children = el.getChildren("case"); |
330 |
| - var branches = new ArrayList<CaseBranch>(children.size()); |
331 |
| - |
332 |
| - for (var innerEl : children) { |
333 |
| - var filter = parser.filter(innerEl, "filter").orNull(); |
334 |
| - var valueRange = XMLUtils.parseNumericRange( |
335 |
| - Node.fromChildOrAttr(innerEl, "match"), Double.class, null); |
336 |
| - if (filter == null && valueRange == null) { |
337 |
| - throw new InvalidXMLException( |
338 |
| - "At least a filter or a value must be specified", innerEl); |
339 |
| - } |
340 |
| - |
341 |
| - if (valueRange != null && formula == null) { |
342 |
| - throw new InvalidXMLException( |
343 |
| - "A value attribute is specified but there's no switch value to bind to", innerEl); |
344 |
| - } |
345 |
| - |
346 |
| - var result = parser.formattedText(innerEl, "result").required(); |
347 |
| - |
348 |
| - branches.add(new CaseBranch( |
349 |
| - result, |
350 |
| - valueRange == null ? Range.all() : valueRange, |
351 |
| - filter == null ? StaticFilter.ALLOW : filter)); |
352 |
| - } |
353 |
| - |
354 |
| - return (T filterable) -> { |
355 |
| - var formulaResult = formula == null ? null : formula.applyAsDouble(filterable); |
356 |
| - for (var branch : branches) { |
357 |
| - if ((formula == null || branch.valueRange.contains(formulaResult)) |
358 |
| - && branch.filter.query(filterable).isAllowed()) return branch.result; |
359 |
| - } |
360 |
| - |
361 |
| - return fallback; |
362 |
| - }; |
363 |
| - } |
364 |
| - default: |
365 |
| - throw new InvalidXMLException("Unknown replacement type", el); |
366 |
| - } |
367 |
| - } |
368 |
| - |
369 | 301 | @MethodParser("sound")
|
370 | 302 | public SoundAction parseSoundAction(Element el, Class<?> scope) throws InvalidXMLException {
|
371 | 303 | SoundType soundType =
|
|
0 commit comments