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