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