001package net.tnemc.plugincore.bukkit.impl;
002
003/*
004 * The New Plugin Core
005 * Copyright (C) 2022 - 2024 Daniel "creatorfromhell" Vidmar
006 *
007 * This program is free software: you can redistribute it and/or modify
008 * it under the terms of the GNU Affero General Public License as published by
009 * the Free Software Foundation, either version 3 of the License, or
010 * (at your option) any later version.
011 *
012 * This program is distributed in the hope that it will be useful,
013 * but WITHOUT ANY WARRANTY; without even the implied warranty of
014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
015 * GNU Affero General Public License for more details.
016 *
017 * You should have received a copy of the GNU Affero General Public License
018 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
019 */
020
021import net.tnemc.item.AbstractItemStack;
022import net.tnemc.item.bukkit.BukkitCalculationsProvider;
023import net.tnemc.item.bukkit.BukkitItemStack;
024import net.tnemc.plugincore.PluginCore;
025import net.tnemc.plugincore.bukkit.BukkitPluginCore;
026import net.tnemc.plugincore.bukkit.hook.PAPIParser;
027import net.tnemc.plugincore.bukkit.impl.scheduler.BukkitScheduler;
028import net.tnemc.plugincore.core.compatibility.CmdSource;
029import net.tnemc.plugincore.core.compatibility.PlayerProvider;
030import net.tnemc.plugincore.core.compatibility.ProxyProvider;
031import net.tnemc.plugincore.core.compatibility.ServerConnector;
032import net.tnemc.plugincore.core.compatibility.WorldProvider;
033import net.tnemc.plugincore.core.compatibility.helper.CraftingRecipe;
034import net.tnemc.plugincore.core.compatibility.scheduler.SchedulerProvider;
035import org.bukkit.Bukkit;
036import org.bukkit.ChatColor;
037import org.bukkit.Material;
038import org.bukkit.NamespacedKey;
039import org.bukkit.OfflinePlayer;
040import org.bukkit.World;
041import org.bukkit.entity.Player;
042import org.bukkit.inventory.ItemStack;
043import org.bukkit.inventory.ShapedRecipe;
044import org.bukkit.inventory.ShapelessRecipe;
045import org.jetbrains.annotations.NotNull;
046import org.jetbrains.annotations.Nullable;
047import revxrsal.commands.bukkit.actor.BukkitCommandActor;
048import revxrsal.commands.command.CommandActor;
049
050import java.io.InputStream;
051import java.util.Map;
052import java.util.Optional;
053import java.util.Set;
054import java.util.UUID;
055import java.util.stream.Collectors;
056
057/**
058 * BukkitServerProvider
059 *
060 * @author creatorfromhell
061 * @since 0.1.2.0
062 */
063public class BukkitServerProvider implements ServerConnector {
064
065  protected final BukkitCalculationsProvider calc = new BukkitCalculationsProvider();
066  protected final BukkitProxyProvider proxy = new BukkitProxyProvider();
067
068  protected final BukkitScheduler scheduler;
069
070  protected String world = null;
071
072  public BukkitServerProvider() {
073
074    this.scheduler = new BukkitScheduler();
075  }
076
077  public BukkitServerProvider(final BukkitScheduler scheduler) {
078
079    this.scheduler = scheduler;
080  }
081
082  public void setDefaultWorld(final String world) {
083
084    this.world = world;
085  }
086
087  @Override
088  public String name() {
089
090    return "bukkit";
091  }
092
093  /**
094   * Finds a WorldProvider object based on the provided world name.
095   *
096   * @param world The name of the world to search for.
097   *
098   * @return An Optional containing the located WorldProvider object if found, or an empty Optional
099   * otherwise.
100   */
101  @Override
102  public Optional<WorldProvider> findWorld(final String world) {
103
104    final World worldObj = Bukkit.getWorld(world);
105    if(worldObj == null) {
106      return Optional.empty();
107    }
108
109    return Optional.of(new BukkitWorldProvider(worldObj));
110  }
111
112  /**
113   * Used to replace placeholders from a string.
114   *
115   * @param player  The player to use for the placeholder replacement.
116   * @param message The message to replace placeholders in.
117   *
118   * @return The string after placeholders have been replaced.
119   */
120  @Override
121  public String replacePlaceholder(final UUID player, final String message) {
122
123    if(player == null) return message;
124
125    final Optional<PlayerProvider> playerOpt = PluginCore.server().findPlayer(player);
126    if(Bukkit.getPluginManager().isPluginEnabled("PlaceholderAPI") && playerOpt.isPresent()
127       && playerOpt.get() instanceof final BukkitPlayerProvider bukkitPlayer) {
128
129      return PAPIParser.parse(bukkitPlayer, message);
130    }
131    return message;
132  }
133
134  /**
135   * The proxy provider to use for this implementation.
136   *
137   * @return The proxy provider to use for this implementation.
138   */
139  @Override
140  public ProxyProvider proxy() {
141
142    return proxy;
143  }
144
145  /**
146   * Used to convert an {@link CommandActor} to a {@link CmdSource}.
147   *
148   * @param actor The command actor.
149   *
150   * @return The {@link CmdSource} for this actor.
151   */
152  @Override
153  public CmdSource<?> source(@NotNull final CommandActor actor) {
154
155    return new BukkitCMDSource((BukkitCommandActor)actor);
156  }
157
158  /**
159   * Retrieves a set of online player names.
160   *
161   * @return Set of online player names.
162   */
163  @Override
164  public Set<String> onlinePlayersList() {
165
166    return Bukkit.getOnlinePlayers().stream()
167            .map(Player::getName)
168            .collect(Collectors.toSet());
169  }
170
171  /**
172   * Used to get the amount of online players.
173   *
174   * @return The amount of online players.
175   */
176  @Override
177  public int onlinePlayers() {
178
179    return Bukkit.getOnlinePlayers().size();
180  }
181
182  /**
183   * Attempts to find a {@link PlayerProvider player} based on an {@link UUID identifier}.
184   *
185   * @param identifier The identifier
186   *
187   * @return An Optional containing the located {@link PlayerProvider player}, or an empty Optional
188   * if no player is located.
189   */
190  @Override
191  public Optional<PlayerProvider> findPlayer(@NotNull final UUID identifier) {
192
193    return Optional.of(BukkitPlayerProvider.find(identifier.toString()));
194  }
195
196  /**
197   * This is used to return an instance of an {@link PlayerProvider player} based on the provided
198   * instance's player object.
199   *
200   * @param player The instance of the player.
201   *
202   * @return The initialized {@link PlayerProvider player object}.
203   */
204  @Override
205  public PlayerProvider initializePlayer(@NotNull final Object player) {
206
207    if(player instanceof final Player playerObj) {
208      return new BukkitPlayerProvider(playerObj);
209    }
210    return null;
211  }
212
213  /**
214   * Creates a custom texture for a specific UUID.
215   *
216   * @param identifier The UUID to create the custom texture for.
217   * @param username   The username of the profile for which the custom texture is being created.
218   * @param texture    The custom texture data to apply to the profile.
219   */
220  @Override
221  public void createCustomTexture(final UUID identifier, final String username, final String texture) {
222    //TODO: This
223  }
224
225  /**
226   * Used to determine if this player has played on this server before.
227   *
228   * @param uuid The {@link UUID} that is associated with the player.
229   *
230   * @return True if the player has played on the server before, otherwise false.
231   */
232  @Override
233  public boolean playedBefore(final UUID uuid) {
234
235    final OfflinePlayer player = Bukkit.getOfflinePlayer(uuid);
236    return player.hasPlayedBefore();
237  }
238
239  /**
240   * Used to determine if a player with the specified username has played before.
241   *
242   * @param name The username to search for.
243   *
244   * @return True if someone with the specified username has played before, otherwise false.
245   */
246  @Override
247  public boolean playedBefore(final String name) {
248
249    final OfflinePlayer player = Bukkit.getOfflinePlayer(name);
250    return player.hasPlayedBefore();
251  }
252
253  /**
254   * Used to determine if a player with the specified username is online.
255   *
256   * @param name The username to search for.
257   *
258   * @return True if someone with the specified username is online.
259   */
260  @Override
261  public boolean online(final String name) {
262
263    try {
264
265      final UUID id = UUID.fromString(name);
266      return Bukkit.getPlayer(id) != null;
267    } catch(final Exception ignore) {
268      return Bukkit.getPlayer(name) != null;
269    }
270  }
271
272  @Override
273  public Optional<UUID> fromName(final String name) {
274
275    for(final OfflinePlayer player : Bukkit.getServer().getOfflinePlayers()) {
276      if(player.getName() == null) continue;
277      if(player.getName().equalsIgnoreCase(name)) {
278        return Optional.of(player.getUniqueId());
279      }
280    }
281    return Optional.empty();
282  }
283
284  /**
285   * Used to locate a username for a specific name. This could be called from either a primary or
286   * secondary thread, and should not call back to the Mojang API ever.
287   *
288   * @param id The {@link UUID} to use for the search.
289   *
290   * @return An optional containing the name if exists, otherwise false.
291   */
292  @Override
293  public Optional<String> fromID(final UUID id) {
294
295    for(final OfflinePlayer player : Bukkit.getServer().getOfflinePlayers()) {
296      if(player.getUniqueId().equals(id)) {
297        return Optional.ofNullable(player.getName());
298      }
299    }
300    return Optional.empty();
301  }
302
303  /**
304   * Returns the name of the default world.
305   *
306   * @return The name of the default world.
307   */
308  @Override
309  public String defaultWorld() {
310
311    if(world == null) {
312      world = Bukkit.getServer().getWorlds().get(0).getName();
313    }
314    return world;
315    /*
316
317    if(world == null) {
318
319      final Properties props = new Properties();
320      try {
321
322        props.load(new FileInputStream(new File(".", "server.properties")));
323      } catch(IOException ignore) {
324      }
325      world = props.getProperty("level-name");
326    }
327    return world;*/
328  }
329
330  /**
331   * Determines if a plugin with the correlating name is currently installed.
332   *
333   * @param name The name to use for our check.
334   *
335   * @return True if a plugin with that name exists, otherwise false.
336   */
337  @Override
338  public boolean pluginAvailable(final String name) {
339
340    return Bukkit.getPluginManager().isPluginEnabled(name);
341  }
342
343  /**
344   * Used to replace colour codes in a string.
345   *
346   * @param string The string to format.
347   * @param strip  If true, the color codes are striped from the string.
348   *
349   * @return The formatted string.
350   */
351  @Override
352  public String replaceColours(final String string, final boolean strip) {
353
354    if(strip) {
355      return ChatColor.stripColor(string);
356    }
357    return ChatColor.translateAlternateColorCodes('&', string);
358  }
359
360  @Override
361  public AbstractItemStack<?> stackBuilder() {
362
363    return new BukkitItemStack();
364  }
365
366  @Override
367  public void saveResource(final String path, final boolean replace) {
368
369    BukkitPluginCore.instance().getPlugin().saveResource(path, replace);
370  }
371
372  /**
373   * Retrieves an input stream for the specified filename from the resources.
374   *
375   * @param filename The name of the file to retrieve from the resources. Cannot be null.
376   *
377   * @return An input stream for the specified file, or null if the file is not found in the
378   * resources.
379   */
380  @Override
381  public @Nullable InputStream getResource(@NotNull final String filename) {
382
383    return BukkitPluginCore.instance().getPlugin().getResource(filename);
384  }
385
386  /**
387   * Provides this implementation's {@link SchedulerProvider scheduler}.
388   *
389   * @return The scheduler for this implementation.
390   */
391  @Override
392  public SchedulerProvider<?> scheduler() {
393
394    return scheduler;
395  }
396
397  /**
398   * Used to register a crafting recipe to the server.
399   *
400   * @param key    The key for the crafting recipe to be registered.
401   * @param recipe The crafting recipe to register.
402   *
403   * @see CraftingRecipe
404   */
405  @Override
406  public void registerCrafting(@NotNull final String key, @NotNull final CraftingRecipe recipe) {
407
408    if(recipe.isShaped()) {
409      ShapedRecipe shaped;
410
411      try {
412        shaped = new ShapedRecipe(new NamespacedKey(BukkitPluginCore.instance().getPlugin(), key), (ItemStack)recipe.getResult().cacheLocale());
413      } catch(final Exception ignore) {
414        shaped = new ShapedRecipe((ItemStack)recipe.getResult().cacheLocale());
415      }
416
417      shaped.shape(recipe.getRows());
418
419      for(final Map.Entry<Character, String> ingredient : recipe.getIngredients().entrySet()) {
420        shaped.setIngredient(ingredient.getKey(), Material.valueOf(ingredient.getValue()));
421      }
422      Bukkit.getServer().addRecipe(shaped);
423    } else {
424      ShapelessRecipe shapeless;
425
426      try {
427        shapeless = new ShapelessRecipe(new NamespacedKey(BukkitPluginCore.instance().getPlugin(), key), (ItemStack)recipe.getResult().cacheLocale());
428      } catch(final Exception ignore) {
429        shapeless = new ShapelessRecipe((ItemStack)recipe.getResult().cacheLocale());
430      }
431
432      for(final Map.Entry<Character, String> ingredient : recipe.getIngredients().entrySet()) {
433        shapeless.addIngredient(1, Material.valueOf(ingredient.getValue()));
434      }
435      Bukkit.getServer().addRecipe(shapeless);
436    }
437  }
438
439  @Override
440  public BukkitCalculationsProvider calculations() {
441
442    return calc;
443  }
444}