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.DoneFlag; 035import com.plotsquared.core.plot.flag.implementations.ServerPlotFlag; 036import com.plotsquared.core.util.EventDispatcher; 037import com.plotsquared.core.util.PlayerManager; 038import com.plotsquared.core.util.query.PlotQuery; 039import net.kyori.adventure.text.Component; 040import org.checkerframework.checker.nullness.qual.NonNull; 041import org.checkerframework.checker.nullness.qual.Nullable; 042 043import java.math.BigDecimal; 044import java.math.RoundingMode; 045import java.text.SimpleDateFormat; 046import java.util.Collection; 047import java.util.Collections; 048import java.util.Locale; 049import java.util.Map; 050import java.util.TimeZone; 051import java.util.UUID; 052import java.util.concurrent.TimeUnit; 053import java.util.function.BiFunction; 054 055/** 056 * Registry that contains {@link Placeholder placeholders} 057 */ 058@Singleton 059public final class PlaceholderRegistry { 060 061 private final Map<String, Placeholder> placeholders; 062 private final EventDispatcher eventDispatcher; 063 064 @Inject 065 public PlaceholderRegistry(final @NonNull EventDispatcher eventDispatcher) { 066 this.placeholders = Maps.newHashMap(); 067 this.eventDispatcher = eventDispatcher; 068 this.registerDefault(); 069 } 070 071 /** 072 * Converts a {@link Component} into a legacy-formatted string. 073 * 074 * @param caption the caption key. 075 * @param localeHolder the locale holder to get the component for 076 * @return a legacy-formatted string. 077 */ 078 private static String legacyComponent(TranslatableCaption caption, LocaleHolder localeHolder) { 079 return PlotSquared.platform().toLegacyPlatformString(caption.toComponent(localeHolder).asComponent()); 080 } 081 082 private void registerDefault() { 083 final GlobalFlagContainer globalFlagContainer = GlobalFlagContainer.getInstance(); 084 for (final PlotFlag<?, ?> flag : globalFlagContainer.getRecognizedPlotFlags()) { 085 this.registerPlaceholder(new PlotFlagPlaceholder(flag, true)); 086 this.registerPlaceholder(new PlotFlagPlaceholder(flag, false)); 087 } 088 GlobalFlagContainer.getInstance().subscribe((flag, type) -> { 089 this.registerPlaceholder(new PlotFlagPlaceholder(flag, true)); 090 this.registerPlaceholder(new PlotFlagPlaceholder(flag, false)); 091 }); 092 this.createPlaceholder("world_name", player -> player.getLocation().getWorldName()); 093 this.createPlaceholder("has_plot", player -> player.getPlotCount() > 0 ? "true" : "false"); 094 this.createPlaceholder("allowed_plot_count", (player) -> { 095 if (player.getAllowedPlots() >= Integer.MAX_VALUE) { // Beautifies cases with '*' permission 096 return legacyComponent(TranslatableCaption.of("info.infinite"), player); 097 } 098 return Integer.toString(player.getAllowedPlots()); 099 }); 100 this.createPlaceholder("base_plot_count", player -> Integer.toString(PlotQuery.newQuery() 101 .ownedBy(player) 102 .whereBasePlot() 103 .thatPasses(plot -> !DoneFlag.isDone(plot)) 104 .count()) 105 ); 106 this.createPlaceholder("plot_count", player -> Integer.toString(player.getPlotCount())); 107 this.createPlaceholder("currentplot_alias", (player, plot) -> { 108 if (plot.getAlias().isEmpty()) { 109 return legacyComponent(TranslatableCaption.of("info.none"), player); 110 } 111 return plot.getAlias(); 112 }); 113 this.createPlaceholder("currentplot_owner", (player, plot) -> { 114 if (plot.getFlag(ServerPlotFlag.class)) { 115 return legacyComponent(TranslatableCaption.of("info.server"), player); 116 } 117 final UUID plotOwner = plot.getOwnerAbs(); 118 if (plotOwner == null) { 119 return legacyComponent(TranslatableCaption.of("generic.generic_unowned"), player); 120 } 121 try { 122 return PlotSquared.platform().playerManager().getUsernameCaption(plotOwner) 123 .get(Settings.UUID.BLOCKING_TIMEOUT, TimeUnit.MILLISECONDS).getComponent(player); 124 } catch (final Exception ignored) { 125 } 126 return legacyComponent(TranslatableCaption.of("info.unknown"), player); 127 }); 128 this.createPlaceholder("currentplot_members", (player, plot) -> { 129 if (plot.getMembers().isEmpty() && plot.getTrusted().isEmpty()) { 130 return legacyComponent(TranslatableCaption.of("info.none"), player); 131 } 132 return String.valueOf(plot.getMembers().size() + plot.getTrusted().size()); 133 }); 134 this.createPlaceholder("currentplot_members_added", (player, plot) -> { 135 if (plot.getMembers().isEmpty()) { 136 return legacyComponent(TranslatableCaption.of("info.none"), player); 137 } 138 return String.valueOf(plot.getMembers().size()); 139 }); 140 this.createPlaceholder("currentplot_members_trusted", (player, plot) -> { 141 if (plot.getTrusted().isEmpty()) { 142 return legacyComponent(TranslatableCaption.of("info.none"), player); 143 } 144 return String.valueOf(plot.getTrusted().size()); 145 }); 146 this.createPlaceholder("currentplot_members_denied", (player, plot) -> { 147 if (plot.getDenied().isEmpty()) { 148 return legacyComponent(TranslatableCaption.of("info.none"), player); 149 } 150 return String.valueOf(plot.getDenied().size()); 151 }); 152 this.createPlaceholder("currentplot_members_trusted_list", (player, plot) -> { 153 if (plot.getTrusted().isEmpty()) { 154 return legacyComponent(TranslatableCaption.of("info.none"), player); 155 } 156 return PlotSquared.platform().toLegacyPlatformString( 157 PlayerManager.getPlayerList(plot.getTrusted(), player)); 158 }); 159 this.createPlaceholder("currentplot_members_added_list", (player, plot) -> { 160 if (plot.getMembers().isEmpty()) { 161 return legacyComponent(TranslatableCaption.of("info.none"), player); 162 } 163 return PlotSquared.platform().toLegacyPlatformString( 164 PlayerManager.getPlayerList(plot.getMembers(), player)); 165 }); 166 this.createPlaceholder("currentplot_members_denied_list", (player, plot) -> { 167 if (plot.getDenied().isEmpty()) { 168 return legacyComponent(TranslatableCaption.of("info.none"), player); 169 } 170 return PlotSquared.platform().toLegacyPlatformString( 171 PlayerManager.getPlayerList(plot.getDenied(), player)); 172 }); 173 this.createPlaceholder("currentplot_creationdate", (player, plot) -> { 174 if (plot.getTimestamp() == 0 || !plot.hasOwner()) { 175 return legacyComponent(TranslatableCaption.of("info.unknown"), player); 176 } 177 long creationDate = plot.getTimestamp(); 178 SimpleDateFormat sdf = new SimpleDateFormat(Settings.Timeformat.DATE_FORMAT); 179 sdf.setTimeZone(TimeZone.getTimeZone(Settings.Timeformat.TIME_ZONE)); 180 return sdf.format(creationDate); 181 }); 182 this.createPlaceholder("currentplot_can_build", (player, plot) -> 183 plot.isAdded(player.getUUID()) ? "true" : "false"); 184 this.createPlaceholder("currentplot_x", (player, plot) -> Integer.toString(plot.getId().getX())); 185 this.createPlaceholder("currentplot_y", (player, plot) -> Integer.toString(plot.getId().getY())); 186 this.createPlaceholder("currentplot_xy", (player, plot) -> plot.getId().toString()); 187 this.createPlaceholder("currentplot_rating", (player, plot) -> { 188 if (Double.isNaN(plot.getAverageRating())) { 189 return legacyComponent(TranslatableCaption.of("placeholder.nan"), player); 190 } 191 BigDecimal roundRating = BigDecimal.valueOf(plot.getAverageRating()).setScale(2, RoundingMode.HALF_UP); 192 if (!Settings.General.SCIENTIFIC) { 193 return String.valueOf(roundRating); 194 } else { 195 return Double.toString(plot.getAverageRating()); 196 } 197 }); 198 this.createPlaceholder("currentplot_biome", (player, plot) -> plot.getBiomeSynchronous().toString()); 199 this.createPlaceholder("currentplot_size", (player, plot) -> String.valueOf(plot.getConnectedPlots().size())); 200 } 201 202 /** 203 * Create a functional placeholder 204 * 205 * @param key Placeholder key 206 * @param placeholderFunction Placeholder generator. Cannot return null 207 */ 208 @SuppressWarnings("ALL") 209 public void createPlaceholder( 210 final @NonNull String key, 211 final @NonNull Function<PlotPlayer<?>, String> placeholderFunction 212 ) { 213 this.registerPlaceholder(new Placeholder(key) { 214 @Override 215 public @NonNull String getValue(final @NonNull PlotPlayer<?> player) { 216 return placeholderFunction.apply(player); 217 } 218 }); 219 } 220 221 /** 222 * Create a functional placeholder 223 * 224 * @param key Placeholder key 225 * @param placeholderFunction Placeholder generator. Cannot return null 226 */ 227 public void createPlaceholder( 228 final @NonNull String key, 229 final @NonNull BiFunction<PlotPlayer<?>, Plot, String> placeholderFunction 230 ) { 231 this.registerPlaceholder(new PlotSpecificPlaceholder(key) { 232 @Override 233 public @NonNull String getValue(final @NonNull PlotPlayer<?> player, final @NonNull Plot plot) { 234 return placeholderFunction.apply(player, plot); 235 } 236 }); 237 } 238 239 /** 240 * Register a placeholder 241 * 242 * @param placeholder Placeholder instance 243 */ 244 public void registerPlaceholder(final @NonNull Placeholder placeholder) { 245 final Placeholder previous = this.placeholders 246 .put( 247 placeholder.getKey().toLowerCase(Locale.ENGLISH), 248 Preconditions.checkNotNull(placeholder, "Placeholder may not be null") 249 ); 250 if (previous == null) { 251 this.eventDispatcher.callGenericEvent(new PlaceholderAddedEvent(placeholder)); 252 } 253 } 254 255 /** 256 * Get a placeholder instance from its key 257 * 258 * @param key Placeholder key 259 * @return Placeholder value 260 */ 261 public @Nullable Placeholder getPlaceholder(final @NonNull String key) { 262 return this.placeholders.get( 263 Preconditions.checkNotNull(key, "Key may not be null").toLowerCase(Locale.ENGLISH)); 264 } 265 266 /** 267 * Get the placeholder value evaluated for a player, and catch and deal with any problems 268 * occurring while doing so 269 * 270 * @param key Placeholder key 271 * @param player Player to evaluate for 272 * @return Replacement value 273 */ 274 public @NonNull String getPlaceholderValue( 275 final @NonNull String key, 276 final @NonNull PlotPlayer<?> player 277 ) { 278 final Placeholder placeholder = getPlaceholder(key); 279 if (placeholder == null) { 280 return ""; 281 } 282 String placeholderValue = ""; 283 try { 284 placeholderValue = placeholder.getValue(player); 285 // If a placeholder for some reason decides to be disobedient, we catch it here 286 if (placeholderValue == null) { 287 new RuntimeException(String 288 .format("Placeholder '%s' returned null for player '%s'", placeholder.getKey(), 289 player.getName() 290 )).printStackTrace(); 291 } 292 } catch (final Exception exception) { 293 new RuntimeException(String 294 .format("Placeholder '%s' failed to evalulate for player '%s'", 295 placeholder.getKey(), player.getName() 296 ), exception).printStackTrace(); 297 } 298 return placeholderValue; 299 } 300 301 /** 302 * Get all placeholders 303 * 304 * @return Unmodifiable collection of placeholders 305 */ 306 public @NonNull Collection<Placeholder> getPlaceholders() { 307 return Collections.unmodifiableCollection(this.placeholders.values()); 308 } 309 310 /** 311 * Event called when a new {@link Placeholder} has been added 312 */ 313 public record PlaceholderAddedEvent( 314 Placeholder placeholder 315 ) { 316 317 } 318 319}