001/* 002 * PlotSquared, a land and world management plugin for Minecraft. 003 * Copyright (C) IntellectualSites <https://intellectualsites.com> 004 * Copyright (C) IntellectualSites team and contributors 005 * 006 * This program is free software: you can redistribute it and/or modify 007 * it under the terms of the GNU General Public License as published by 008 * the Free Software Foundation, either version 3 of the License, or 009 * (at your option) any later version. 010 * 011 * This program is distributed in the hope that it will be useful, 012 * but WITHOUT ANY WARRANTY; without even the implied warranty of 013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 014 * GNU General Public License for more details. 015 * 016 * You should have received a copy of the GNU General Public License 017 * along with this program. If not, see <https://www.gnu.org/licenses/>. 018 */ 019package com.plotsquared.core.util.placeholders; 020 021import com.google.common.base.Function; 022import com.google.common.base.Preconditions; 023import com.google.common.collect.Maps; 024import com.google.inject.Inject; 025import com.google.inject.Singleton; 026import com.plotsquared.core.PlotSquared; 027import com.plotsquared.core.configuration.Settings; 028import com.plotsquared.core.configuration.caption.LocaleHolder; 029import com.plotsquared.core.configuration.caption.TranslatableCaption; 030import com.plotsquared.core.player.PlotPlayer; 031import com.plotsquared.core.plot.Plot; 032import com.plotsquared.core.plot.flag.GlobalFlagContainer; 033import com.plotsquared.core.plot.flag.PlotFlag; 034import com.plotsquared.core.plot.flag.implementations.ServerPlotFlag; 035import com.plotsquared.core.util.EventDispatcher; 036import com.plotsquared.core.util.PlayerManager; 037import net.kyori.adventure.text.Component; 038import net.kyori.adventure.text.minimessage.MiniMessage; 039import org.checkerframework.checker.nullness.qual.NonNull; 040import org.checkerframework.checker.nullness.qual.Nullable; 041 042import java.math.BigDecimal; 043import java.math.RoundingMode; 044import java.text.SimpleDateFormat; 045import java.util.Collection; 046import java.util.Collections; 047import java.util.Locale; 048import java.util.Map; 049import java.util.TimeZone; 050import java.util.UUID; 051import java.util.function.BiFunction; 052 053/** 054 * Registry that contains {@link Placeholder placeholders} 055 */ 056@Singleton 057public final class PlaceholderRegistry { 058 059 private final Map<String, Placeholder> placeholders; 060 private final EventDispatcher eventDispatcher; 061 062 @Inject 063 public PlaceholderRegistry(final @NonNull EventDispatcher eventDispatcher) { 064 this.placeholders = Maps.newHashMap(); 065 this.eventDispatcher = eventDispatcher; 066 this.registerDefault(); 067 } 068 069 /** 070 * Converts a {@link Component} into a legacy-formatted string. 071 * 072 * @param caption the caption key. 073 * @param localeHolder the locale holder to get the component for 074 * @return a legacy-formatted string. 075 */ 076 private static String legacyComponent(TranslatableCaption caption, LocaleHolder localeHolder) { 077 Component component = MiniMessage.get().parse(caption.getComponent(localeHolder)); 078 return PlotSquared.platform().toLegacyPlatformString(component); 079 } 080 081 private void registerDefault() { 082 final GlobalFlagContainer globalFlagContainer = GlobalFlagContainer.getInstance(); 083 for (final PlotFlag<?, ?> flag : globalFlagContainer.getRecognizedPlotFlags()) { 084 this.registerPlaceholder(new PlotFlagPlaceholder(flag, true)); 085 this.registerPlaceholder(new PlotFlagPlaceholder(flag, false)); 086 } 087 GlobalFlagContainer.getInstance().subscribe((flag, type) -> { 088 this.registerPlaceholder(new PlotFlagPlaceholder(flag, true)); 089 this.registerPlaceholder(new PlotFlagPlaceholder(flag, false)); 090 }); 091 this.createPlaceholder("world_name", player -> player.getLocation().getWorldName()); 092 this.createPlaceholder("has_plot", player -> player.getPlotCount() > 0 ? "true" : "false"); 093 this.createPlaceholder("allowed_plot_count", (player) -> { 094 if (player.getAllowedPlots() >= Integer.MAX_VALUE) { // Beautifies cases with '*' permission 095 return legacyComponent(TranslatableCaption.of("info.infinite"), player); 096 } 097 return Integer.toString(player.getAllowedPlots()); 098 }); 099 this.createPlaceholder("plot_count", player -> Integer.toString(player.getPlotCount())); 100 this.createPlaceholder("currentplot_alias", (player, plot) -> { 101 if (plot.getAlias().isEmpty()) { 102 return legacyComponent(TranslatableCaption.of("info.none"), player); 103 } 104 return plot.getAlias(); 105 }); 106 this.createPlaceholder("currentplot_owner", (player, plot) -> { 107 if (plot.getFlag(ServerPlotFlag.class)){ 108 return legacyComponent(TranslatableCaption.of("info.server"), player); 109 } 110 final UUID plotOwner = plot.getOwnerAbs(); 111 if (plotOwner == null) { 112 return legacyComponent(TranslatableCaption.of("generic.generic_unowned"), player); 113 } 114 115 try { 116 return PlayerManager.resolveName(plotOwner, false).getComponent(player); 117 } catch (final Exception ignored) { 118 } 119 return legacyComponent(TranslatableCaption.of("info.unknown"), player); 120 }); 121 this.createPlaceholder("currentplot_members", (player, plot) -> { 122 if (plot.getMembers().isEmpty() && plot.getTrusted().isEmpty()) { 123 return legacyComponent(TranslatableCaption.of("info.none"), player); 124 } 125 return String.valueOf(plot.getMembers().size() + plot.getTrusted().size()); 126 }); 127 this.createPlaceholder("currentplot_members_added", (player, plot) -> { 128 if (plot.getMembers().isEmpty()) { 129 return legacyComponent(TranslatableCaption.of("info.none"), player); 130 } 131 return String.valueOf(plot.getMembers().size()); 132 }); 133 this.createPlaceholder("currentplot_members_trusted", (player, plot) -> { 134 if (plot.getTrusted().isEmpty()) { 135 return legacyComponent(TranslatableCaption.of("info.none"), player); 136 } 137 return String.valueOf(plot.getTrusted().size()); 138 }); 139 this.createPlaceholder("currentplot_members_denied", (player, plot) -> { 140 if (plot.getDenied().isEmpty()) { 141 return legacyComponent(TranslatableCaption.of("info.none"), player); 142 } 143 return String.valueOf(plot.getDenied().size()); 144 }); 145 this.createPlaceholder("currentplot_members_trusted_list", (player, plot) -> { 146 if (plot.getTrusted().isEmpty()) { 147 return legacyComponent(TranslatableCaption.of("info.none"), player); 148 } 149 return PlotSquared.platform().toLegacyPlatformString( 150 PlayerManager.getPlayerList(plot.getTrusted(), player)); 151 }); 152 this.createPlaceholder("currentplot_members_added_list", (player, plot) -> { 153 if (plot.getMembers().isEmpty()) { 154 return legacyComponent(TranslatableCaption.of("info.none"), player); 155 } 156 return PlotSquared.platform().toLegacyPlatformString( 157 PlayerManager.getPlayerList(plot.getMembers(), player)); 158 }); 159 this.createPlaceholder("currentplot_members_denied_list", (player, plot) -> { 160 if (plot.getDenied().isEmpty()) { 161 return legacyComponent(TranslatableCaption.of("info.none"), player); 162 } 163 return PlotSquared.platform().toLegacyPlatformString( 164 PlayerManager.getPlayerList(plot.getDenied(), player)); 165 }); 166 this.createPlaceholder("currentplot_creationdate", (player, plot) -> { 167 if (plot.getTimestamp() == 0 || !plot.hasOwner()) { 168 return legacyComponent(TranslatableCaption.of("info.unknown"), player); 169 } 170 long creationDate = plot.getTimestamp(); 171 SimpleDateFormat sdf = new SimpleDateFormat(Settings.Timeformat.DATE_FORMAT); 172 sdf.setTimeZone(TimeZone.getTimeZone(Settings.Timeformat.TIME_ZONE)); 173 return sdf.format(creationDate); 174 }); 175 this.createPlaceholder("currentplot_can_build", (player, plot) -> 176 plot.isAdded(player.getUUID()) ? "true" : "false"); 177 this.createPlaceholder("currentplot_x", (player, plot) -> Integer.toString(plot.getId().getX())); 178 this.createPlaceholder("currentplot_y", (player, plot) -> Integer.toString(plot.getId().getY())); 179 this.createPlaceholder("currentplot_xy", (player, plot) -> plot.getId().toString()); 180 this.createPlaceholder("currentplot_rating", (player, plot) -> { 181 if (Double.isNaN(plot.getAverageRating())) { 182 return legacyComponent(TranslatableCaption.of("placeholder.nan"), player); 183 } 184 BigDecimal roundRating = BigDecimal.valueOf(plot.getAverageRating()).setScale(2, RoundingMode.HALF_UP); 185 if (!Settings.General.SCIENTIFIC) { 186 return String.valueOf(roundRating); 187 } else { 188 return Double.toString(plot.getAverageRating()); 189 } 190 }); 191 this.createPlaceholder("currentplot_biome", (player, plot) -> plot.getBiomeSynchronous().toString()); 192 } 193 194 /** 195 * Create a functional placeholder 196 * 197 * @param key Placeholder key 198 * @param placeholderFunction Placeholder generator. Cannot return null 199 */ 200 @SuppressWarnings("ALL") 201 public void createPlaceholder( 202 final @NonNull String key, 203 final @NonNull Function<PlotPlayer<?>, String> placeholderFunction 204 ) { 205 this.registerPlaceholder(new Placeholder(key) { 206 @Override 207 public @NonNull String getValue(final @NonNull PlotPlayer<?> player) { 208 return placeholderFunction.apply(player); 209 } 210 }); 211 } 212 213 /** 214 * Create a functional placeholder 215 * 216 * @param key Placeholder key 217 * @param placeholderFunction Placeholder generator. Cannot return null 218 */ 219 public void createPlaceholder( 220 final @NonNull String key, 221 final @NonNull BiFunction<PlotPlayer<?>, Plot, String> placeholderFunction 222 ) { 223 this.registerPlaceholder(new PlotSpecificPlaceholder(key) { 224 @Override 225 public @NonNull String getValue(final @NonNull PlotPlayer<?> player, final @NonNull Plot plot) { 226 return placeholderFunction.apply(player, plot); 227 } 228 }); 229 } 230 231 /** 232 * Register a placeholder 233 * 234 * @param placeholder Placeholder instance 235 */ 236 public void registerPlaceholder(final @NonNull Placeholder placeholder) { 237 final Placeholder previous = this.placeholders 238 .put( 239 placeholder.getKey().toLowerCase(Locale.ENGLISH), 240 Preconditions.checkNotNull(placeholder, "Placeholder may not be null") 241 ); 242 if (previous == null) { 243 this.eventDispatcher.callGenericEvent(new PlaceholderAddedEvent(placeholder)); 244 } 245 } 246 247 /** 248 * Get a placeholder instance from its key 249 * 250 * @param key Placeholder key 251 * @return Placeholder value 252 */ 253 public @Nullable Placeholder getPlaceholder(final @NonNull String key) { 254 return this.placeholders.get( 255 Preconditions.checkNotNull(key, "Key may not be null").toLowerCase(Locale.ENGLISH)); 256 } 257 258 /** 259 * Get the placeholder value evaluated for a player, and catch and deal with any problems 260 * occurring while doing so 261 * 262 * @param key Placeholder key 263 * @param player Player to evaluate for 264 * @return Replacement value 265 */ 266 public @NonNull String getPlaceholderValue( 267 final @NonNull String key, 268 final @NonNull PlotPlayer<?> player 269 ) { 270 final Placeholder placeholder = getPlaceholder(key); 271 if (placeholder == null) { 272 return ""; 273 } 274 String placeholderValue = ""; 275 try { 276 placeholderValue = placeholder.getValue(player); 277 // If a placeholder for some reason decides to be disobedient, we catch it here 278 if (placeholderValue == null) { 279 new RuntimeException(String 280 .format("Placeholder '%s' returned null for player '%s'", placeholder.getKey(), 281 player.getName() 282 )).printStackTrace(); 283 } 284 } catch (final Exception exception) { 285 new RuntimeException(String 286 .format("Placeholder '%s' failed to evalulate for player '%s'", 287 placeholder.getKey(), player.getName() 288 ), exception).printStackTrace(); 289 } 290 return placeholderValue; 291 } 292 293 /** 294 * Get all placeholders 295 * 296 * @return Unmodifiable collection of placeholders 297 */ 298 public @NonNull Collection<Placeholder> getPlaceholders() { 299 return Collections.unmodifiableCollection(this.placeholders.values()); 300 } 301 302 /** 303 * Event called when a new {@link Placeholder} has been added 304 */ 305 public static class PlaceholderAddedEvent { 306 307 private final Placeholder placeholder; 308 309 public PlaceholderAddedEvent(Placeholder placeholder) { 310 this.placeholder = placeholder; 311 } 312 313 public Placeholder getPlaceholder() { 314 return this.placeholder; 315 } 316 317 } 318 319}