@@ -68,6 +68,7 @@ of this software and associated documentation files (the "Software"), to deal
68
68
import org .spongepowered .api .text .Text ;
69
69
import org .spongepowered .api .text .serializer .TextSerializers ;
70
70
import org .spongepowered .api .world .Locatable ;
71
+ import org .spongepowered .api .world .storage .WorldProperties ;
71
72
72
73
import com .flowpowered .math .vector .Vector3d ;
73
74
@@ -83,13 +84,29 @@ of this software and associated documentation files (the "Software"), to deal
83
84
import me .rojo8399 .placeholderapi .impl .PlaceholderAPIPlugin ;
84
85
import me .rojo8399 .placeholderapi .impl .configs .JavascriptManager ;
85
86
import me .rojo8399 .placeholderapi .impl .configs .Messages ;
87
+ import me .rojo8399 .placeholderapi .impl .utils .TypeUtils ;
86
88
import ninja .leaping .configurate .objectmapping .Setting ;
87
89
import ninja .leaping .configurate .objectmapping .serialize .ConfigSerializable ;
88
90
91
+ /**
92
+ * The default placeholders provided by the plugin. These also server as
93
+ * examples of many, though not all, possibilities for the plugin to use as
94
+ * placeholders.
95
+ *
96
+ * The class is a listener class AND a configurable class, allowing the
97
+ * placeholders to be attached directly to PlaceholderAPI.
98
+ *
99
+ * @author Wundero
100
+ *
101
+ */
89
102
@ Listening
90
103
@ ConfigSerializable
91
104
public class Defaults {
92
105
106
+ /**
107
+ * This class simply represents the service PlaceholderAPI provides, except
108
+ * simplified in order to be used in JavaScript placeholders.
109
+ */
93
110
public static class Service {
94
111
private CommandSource o ;
95
112
private Player p ;
@@ -125,6 +142,12 @@ public String value(String placeholder, String pattern) {
125
142
}
126
143
}
127
144
145
+ /**
146
+ * Dynamic object used for configuring the "server" placeholder. Notice that the
147
+ * fields inside this class do NOT need to be attached to any one placeholder;
148
+ * the fields in this class do not know what placeholder is going to use them so
149
+ * they do not need to be attached.
150
+ */
128
151
@ ConfigSerializable
129
152
private static class Uptime {
130
153
@@ -147,12 +170,26 @@ public String toString() {
147
170
}
148
171
}
149
172
173
+ /**
174
+ * Pattern to match against a sound placeholder containing _all or all_.
175
+ */
150
176
private static final Pattern ALLSOUND_PATTERN = Pattern .compile ("([_]?all[_]?)" , Pattern .CASE_INSENSITIVE );
151
177
178
+ /**
179
+ * Runtime utility to convert to MB from bytes.
180
+ */
152
181
private static int MB = 1024 * 1024 ;
153
182
183
+ /**
184
+ * Current runtime; used by server placeholder.
185
+ */
154
186
private static Runtime runtime = Runtime .getRuntime ();
155
187
188
+ /*
189
+ * Begin utilities methods: these methods are used throughout the class
190
+ * but do not play major roles in explaining the plugin.
191
+ */
192
+
156
193
private static boolean between (double o , double min , double max ) {
157
194
return o >= min && o <= max ;
158
195
}
@@ -265,12 +302,22 @@ private static long getTime(Player player, TimeUnit unit, boolean round) {
265
302
private EconomyService service ;
266
303
private UserStorageService storage = null ;
267
304
305
+ /**
306
+ * This field is what the object will use as a configurable field; fields
307
+ * DIRECTLY inside of the object registered MUST be attached to a placeholder
308
+ * using the Attach annotation. If they are not attached, they will be ignored
309
+ * completely.
310
+ */
268
311
@ Setting
269
- @ Attach ("server" )
312
+ @ Attach ("server" ) // Use the id of the placeholder you would like to attach to. The actual
313
+ // placeholder you attach to is arbitrary, however it must exist within this
314
+ // class.
270
315
private List <Uptime > uptimes = new ArrayList <>();
271
316
272
317
private Set <User > users = new HashSet <>();
273
318
319
+ // Since the object is instantiated before being passed to PlaceholderAPI, you
320
+ // can do whatever for this.
274
321
public Defaults (EconomyService service , JavascriptManager manager , PlaceholderService s ) {
275
322
if (service != null ) {
276
323
eco = true ;
@@ -316,6 +363,43 @@ private boolean contains(Player p) {
316
363
return users .stream ().map (u -> u .getUniqueId ()).anyMatch (p .getUniqueId ()::equals );
317
364
}
318
365
366
+ /**
367
+ * This is the first placeholder in the object. This handles how the placeholder
368
+ * "economy" parses. I return an object to allow me to return any value without
369
+ * having to make a nice supertype.
370
+ *
371
+ * The parameters in this method are annotated with Nullable. If they have this
372
+ * annotation, they can have null values and you should handle accordingly; if
373
+ * they do not have this annotation, you can safely assume those parameters will
374
+ * NEVER be null.
375
+ *
376
+ * @param token
377
+ * This parameter is the part in the placeholder after the first _.
378
+ * For example, {economy_balance} gives a token of "balance". The
379
+ * 'fix = true' parameter tells PlaceholderAPI to make the token
380
+ * lower case and remove excess characters.
381
+ *
382
+ * For the token, you can request types other than String if you
383
+ * want. If you know your token will have multiple _ separated
384
+ * sections, you can request a String[]. If you want a number, you
385
+ * can request a Double or an Integer. PlaceholderAPI will handle the
386
+ * conversion for you. PlaceholderAPI will also try it's best to cast
387
+ * to a given type but, if the type does not have nice
388
+ * deserialization options, may not ever parse the placeholder. If
389
+ * you want the placeholder to parse to any object type, you must
390
+ * register a type serializer into PlaceholderService.
391
+ * @param player
392
+ * This parameter is the Source parameter, or who the placeholder is
393
+ * replacing for. In this case, it would be who's balance to draw
394
+ * from.
395
+ * @return whatever the value of the placeholder is.
396
+ * @throws NoValueException
397
+ * - Throw this exception if you do not want the placeholder
398
+ * replaced. For example, "{economy_a}" should throw a
399
+ * NoValueException so that PlaceholderAPI helps the user fix issues
400
+ * with their placeholders. If you return null, PlaceholderAPI will
401
+ * fill with an empty string.
402
+ */
319
403
@ Placeholder (id = "economy" )
320
404
public Object economy (@ Token (fix = true ) @ Nullable String token , @ Nullable @ Source User player )
321
405
throws NoValueException {
@@ -432,6 +516,27 @@ private long getUptimeMillis() {
432
516
.reduce ((long ) Sponge .getServer ().getRunningTimeTicks () * 50 , (a , b ) -> a + b );
433
517
}
434
518
519
+ /**
520
+ * This is another example of a placeholder. This one is simple in comparison to
521
+ * the others.
522
+ *
523
+ * The Relational annotation forces this to be used with the rel_ prefix in the
524
+ * placeholder, like this: {rel_rank_greater_than}. This provides no guarantees
525
+ * that it will actually need or use both source and observer. Again, all
526
+ * parameters here are null-safe (will not call the method if those parameters
527
+ * are null).
528
+ *
529
+ * @param token
530
+ * This is the token, like in all other placeholders.
531
+ * @param underrank
532
+ * This is the player comparing. For instance, if you say source >
533
+ * observer, source will be a child of observer and thus have more
534
+ * permissions.
535
+ * @param overrank
536
+ * This is the player being compared to.
537
+ * @return Whether the expression, greater_than or less_than, returns true.
538
+ * @throws NoValueException
539
+ */
435
540
@ Placeholder (id = "rank" )
436
541
@ Relational
437
542
public Boolean isAbove (@ Token String token , @ Source User underrank , @ Observer User overrank )
@@ -473,6 +578,10 @@ public Object js(@Nullable @Source Player player, @Nullable @Observer CommandSou
473
578
return manager .eval (engine , token );
474
579
}
475
580
581
+ /**
582
+ * Listener is registered because of the @Listening annotation. No need to
583
+ * attach it to your plugin, PlaceholderAPI will do this for you.
584
+ */
476
585
@ Listener
477
586
public void newEco (ChangeServiceProviderEvent event ) {
478
587
if (event .getService ().equals (EconomyService .class )) {
@@ -557,13 +666,14 @@ public Object normalPlayer(@Source Player p, @Token(fix = true) @Nullable String
557
666
case "remaining_air" :
558
667
return p .getOrElse (Keys .REMAINING_AIR , 300 );
559
668
case "item_in_main_hand" :
669
+ // ItemStack return types are parsed nicely
560
670
return p .getItemInHand (HandTypes .MAIN_HAND ).orElse (ItemStackSnapshot .NONE .createStack ());
561
671
case "item_in_off_hand" :
562
672
return p .getItemInHand (HandTypes .OFF_HAND ).orElse (ItemStackSnapshot .NONE .createStack ());
563
673
case "walk_speed" :
564
674
return p .getOrElse (Keys .WALKING_SPEED , 1.0 );
565
675
case "time_played_seconds" :
566
- return getTime (p , TimeUnit .SECONDS , true );
676
+ return getTime (p , TimeUnit .SECONDS , true ); // Instant and Duration return types are serialized
567
677
case "time_played_minutes" :
568
678
return getTime (p , TimeUnit .MINUTES , true );
569
679
case "time_played_ticks" :
@@ -622,31 +732,6 @@ public void onStopping(GameStoppingEvent event) {
622
732
Store .get ().get ("server" , false ).ifPresent (Expansion ::saveConfig );
623
733
}
624
734
625
- /*
626
- * @Placeholder(id = "playerlist") public List<Player> list(@Nullable @Token(fix
627
- * = true) String token) { if (token == null) { return
628
- * Sponge.getServer().getOnlinePlayers().stream() .filter(p ->
629
- * !p.getOrElse(Keys.VANISH_PREVENTS_TARGETING,
630
- * false)).collect(Collectors.toList()); } Stream<Player> out =
631
- * Sponge.getServer().getOnlinePlayers().stream() .filter(p ->
632
- * !p.getOrElse(Keys.VANISH_PREVENTS_TARGETING, false)); if
633
- * (PERM.matcher(token).find()) { Matcher m = PERM.matcher(token); while
634
- * (m.find()) { String permission = m.group(1); out = out.filter(p ->
635
- * p.hasPermission(permission)); } } if (WORLD.matcher(token).find()) { Matcher
636
- * m = WORLD.matcher(token); while (m.find()) { String world = m.group(1); out =
637
- * out.filter(p -> p.getWorld().getName().toLowerCase().startsWith(world)); } }
638
- * // TODO: /* better token matching data key boolean filter -> load key from t
639
- * - IS_FLYING, for example data key numeric filter -> load key again, but also
640
- * load comparator (>, >=, <, <=, =) and number - Health > 10, for example -
641
- * sort greatest value for key??? better idea??: placeholder value filter ->
642
- * load placeholder from key, load value from key, load comparator sanity checks
643
- * (no > for boolean, no value present = true/0/max int, depending, no comp
644
- * present: =) sort by highest comparison limiter, top X players if available ->
645
- * sort by highest comp then alphabetically
646
- */ /*
647
- * return out.collect(Collectors.toList()); }
648
- */
649
-
650
735
private void putCur (Currency c ) {
651
736
currencies .put (c .getName ().toLowerCase ().replace (" " , "" ), c );
652
737
}
@@ -739,6 +824,46 @@ public Object relPlayer(@Source Player one, @Observer CommandSource two, @Token(
739
824
740
825
@ Placeholder (id = "server" )
741
826
public Object server (@ Token (fix = true ) String identifier ) throws NoValueException {
827
+ boolean gt = false ;
828
+ if (identifier .startsWith ("time" )) {
829
+ gt = true ;
830
+ identifier = identifier .substring ("time" .length ());
831
+ }
832
+ if (identifier .startsWith ("game_time" )) {
833
+ gt = true ;
834
+ identifier = identifier .substring ("game_time" .length ());
835
+ }
836
+ if (gt ) {
837
+ Optional <WorldProperties > w = Sponge .getServer ().getDefaultWorld ();
838
+ if (!identifier .isEmpty ()) {
839
+ String world = identifier .substring (1 );
840
+ if (world .isEmpty ()) {
841
+ if (w .isPresent ()) {
842
+ return w .get ().getWorldTime () % 24000 ;
843
+ } else {
844
+ String id1 = identifier ;
845
+ throw new NoValueException (Messages .get ().misc .invalid .t ("world" ),
846
+ Sponge .getServer ().getAllWorldProperties ().stream ().map (wp -> wp .getWorldName ())
847
+ .filter (n -> TypeUtils .closeTo (id1 .replaceFirst ("_" , "" ), n ))
848
+ .map (s -> "time_" + s ).collect (Collectors .toList ()));
849
+ }
850
+ }
851
+ WorldProperties wp = Sponge .getServer ().getWorld (world ).map (wo -> wo .getProperties ()).orElse (null );
852
+ if (wp != null ) {
853
+ return wp .getWorldTime () % 24000 ;
854
+ }
855
+ } else {
856
+ if (w .isPresent ()) {
857
+ return w .get ().getWorldTime () % 24000 ;
858
+ }
859
+ }
860
+ String id1 = identifier ;
861
+ throw new NoValueException (Messages .get ().misc .invalid .t ("world" ),
862
+ Sponge .getServer ().getAllWorldProperties ().stream ().map (wp -> wp .getWorldName ())
863
+ .filter (n -> n .equalsIgnoreCase (w .get ().getWorldName ())
864
+ || TypeUtils .closeTo (id1 .replaceFirst ("_" , "" ), n ))
865
+ .map (s -> "time_" + s ).collect (Collectors .toList ()));
866
+ }
742
867
switch (identifier ) {
743
868
case "online" :
744
869
return Sponge .getServer ().getOnlinePlayers ().stream ()
@@ -750,7 +875,7 @@ public Object server(@Token(fix = true) String identifier) throws NoValueExcepti
750
875
case "motd" :
751
876
return Sponge .getServer ().getMotd ();
752
877
case "uptime" :
753
- case "uptime_percent" :
878
+ case "uptime_percent" : // Uptime config item used here.
754
879
long um = this .getUptimeMillis ();
755
880
long dm = this .getDowntimeMillis ();
756
881
NumberFormat fmt = NumberFormat .getPercentInstance ();
@@ -830,16 +955,14 @@ public void sync() {
830
955
}
831
956
}
832
957
958
+ /**
959
+ * This method does not need parameters, but can still be called. LocalDateTime
960
+ * is also nicely serialized.
961
+ */
833
962
@ Placeholder (id = "time" )
834
963
public LocalDateTime time () {
835
964
return LocalDateTime .now ();
836
965
}
837
- /*
838
- * private static final Pattern PERM = Pattern.compile(
839
- * "perm(?:ission)?\\_([A-Za-z0-9*\\-]+(?:\\.[A-Za-z0-9*\\-]+)+)",
840
- * Pattern.CASE_INSENSITIVE), WORLD =
841
- * Pattern.compile("world\\_([A-Za-z0-9\\_\\-]+)", Pattern.CASE_INSENSITIVE);
842
- */
843
966
844
967
public int unique () {
845
968
return users .size ();
0 commit comments