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