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