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.bukkit.player; 020 021import com.google.common.base.Charsets; 022import com.plotsquared.bukkit.util.BukkitUtil; 023import com.plotsquared.core.PlotSquared; 024import com.plotsquared.core.configuration.Settings; 025import com.plotsquared.core.events.TeleportCause; 026import com.plotsquared.core.location.Location; 027import com.plotsquared.core.permissions.Permission; 028import com.plotsquared.core.permissions.PermissionHandler; 029import com.plotsquared.core.player.ConsolePlayer; 030import com.plotsquared.core.player.PlotPlayer; 031import com.plotsquared.core.plot.PlotWeather; 032import com.plotsquared.core.plot.world.PlotAreaManager; 033import com.plotsquared.core.util.EventDispatcher; 034import com.plotsquared.core.util.MathMan; 035import com.sk89q.worldedit.bukkit.BukkitAdapter; 036import com.sk89q.worldedit.extension.platform.Actor; 037import com.sk89q.worldedit.world.item.ItemType; 038import com.sk89q.worldedit.world.item.ItemTypes; 039import io.papermc.lib.PaperLib; 040import net.kyori.adventure.audience.Audience; 041import org.bukkit.GameMode; 042import org.bukkit.Sound; 043import org.bukkit.WeatherType; 044import org.bukkit.entity.Player; 045import org.bukkit.event.Event; 046import org.bukkit.event.EventException; 047import org.bukkit.event.player.PlayerTeleportEvent; 048import org.bukkit.permissions.PermissionAttachmentInfo; 049import org.bukkit.plugin.RegisteredListener; 050import org.bukkit.potion.PotionEffectType; 051import org.checkerframework.checker.index.qual.NonNegative; 052import org.checkerframework.checker.nullness.qual.NonNull; 053 054import java.util.Arrays; 055import java.util.Set; 056import java.util.UUID; 057 058import static com.sk89q.worldedit.world.gamemode.GameModes.ADVENTURE; 059import static com.sk89q.worldedit.world.gamemode.GameModes.CREATIVE; 060import static com.sk89q.worldedit.world.gamemode.GameModes.SPECTATOR; 061import static com.sk89q.worldedit.world.gamemode.GameModes.SURVIVAL; 062 063public class BukkitPlayer extends PlotPlayer<Player> { 064 065 private static boolean CHECK_EFFECTIVE = true; 066 public final Player player; 067 private String name; 068 069 /** 070 * @param plotAreaManager PlotAreaManager instance 071 * @param eventDispatcher EventDispatcher instance 072 * @param player Bukkit player instance 073 * @param permissionHandler PermissionHandler instance 074 */ 075 BukkitPlayer( 076 final @NonNull PlotAreaManager plotAreaManager, 077 final @NonNull EventDispatcher eventDispatcher, 078 final @NonNull Player player, 079 final boolean realPlayer, 080 final @NonNull PermissionHandler permissionHandler 081 ) { 082 super(plotAreaManager, eventDispatcher, permissionHandler); 083 this.player = player; 084 this.setupPermissionProfile(); 085 if (realPlayer) { 086 super.populatePersistentMetaMap(); 087 } 088 } 089 090 @Override 091 public Actor toActor() { 092 return BukkitAdapter.adapt(player); 093 } 094 095 @Override 096 public Player getPlatformPlayer() { 097 return this.player; 098 } 099 100 @NonNull 101 @Override 102 public UUID getUUID() { 103 if (Settings.UUID.OFFLINE) { 104 if (Settings.UUID.FORCE_LOWERCASE) { 105 return UUID.nameUUIDFromBytes(("OfflinePlayer:" + 106 getName().toLowerCase()).getBytes(Charsets.UTF_8)); 107 } else { 108 return UUID.nameUUIDFromBytes(("OfflinePlayer:" + 109 getName()).getBytes(Charsets.UTF_8)); 110 } 111 } 112 return player.getUniqueId(); 113 } 114 115 @Override 116 @NonNegative 117 public long getLastPlayed() { 118 return this.player.getLastSeen(); 119 } 120 121 @Override 122 public boolean canTeleport(final @NonNull Location location) { 123 final org.bukkit.Location to = BukkitUtil.adapt(location); 124 final org.bukkit.Location from = player.getLocation(); 125 PlayerTeleportEvent event = new PlayerTeleportEvent(player, from, to); 126 callEvent(event); 127 if (event.isCancelled() || !event.getTo().equals(to)) { 128 return false; 129 } 130 event = new PlayerTeleportEvent(player, to, from); 131 callEvent(event); 132 return true; 133 } 134 135 private void callEvent(final @NonNull Event event) { 136 final RegisteredListener[] listeners = event.getHandlers().getRegisteredListeners(); 137 for (final RegisteredListener listener : listeners) { 138 if (listener.getPlugin().getName().equals(PlotSquared.platform().pluginName())) { 139 continue; 140 } 141 try { 142 listener.callEvent(event); 143 } catch (final EventException e) { 144 e.printStackTrace(); 145 } 146 } 147 } 148 149 @SuppressWarnings("StringSplitter") 150 @Override 151 @NonNegative 152 public int hasPermissionRange( 153 final @NonNull String stub, 154 @NonNegative final int range 155 ) { 156 if (hasPermission(Permission.PERMISSION_ADMIN.toString())) { 157 return Integer.MAX_VALUE; 158 } 159 final String[] nodes = stub.split("\\."); 160 final StringBuilder n = new StringBuilder(); 161 for (int i = 0; i < (nodes.length - 1); i++) { 162 n.append(nodes[i]).append("."); 163 if (!stub.equals(n + Permission.PERMISSION_STAR.toString())) { 164 if (hasPermission(n + Permission.PERMISSION_STAR.toString())) { 165 return Integer.MAX_VALUE; 166 } 167 } 168 } 169 if (hasPermission(stub + ".*")) { 170 return Integer.MAX_VALUE; 171 } 172 int max = 0; 173 if (CHECK_EFFECTIVE) { 174 boolean hasAny = false; 175 String stubPlus = stub + "."; 176 final Set<PermissionAttachmentInfo> effective = player.getEffectivePermissions(); 177 if (!effective.isEmpty()) { 178 for (PermissionAttachmentInfo attach : effective) { 179 // Ignore all "false" permissions 180 if (!attach.getValue()) { 181 continue; 182 } 183 String permStr = attach.getPermission(); 184 if (permStr.startsWith(stubPlus)) { 185 hasAny = true; 186 String end = permStr.substring(stubPlus.length()); 187 if (MathMan.isInteger(end)) { 188 int val = Integer.parseInt(end); 189 if (val > range) { 190 return val; 191 } 192 if (val > max) { 193 max = val; 194 } 195 } 196 } 197 } 198 if (hasAny) { 199 return max; 200 } 201 // Workaround 202 for (PermissionAttachmentInfo attach : effective) { 203 String permStr = attach.getPermission(); 204 if (permStr.startsWith("plots.") && !permStr.equals("plots.use")) { 205 return max; 206 } 207 } 208 CHECK_EFFECTIVE = false; 209 } 210 } 211 for (int i = range; i > 0; i--) { 212 if (hasPermission(stub + "." + i)) { 213 return i; 214 } 215 } 216 return max; 217 } 218 219 @Override 220 public void teleport(final @NonNull Location location, final @NonNull TeleportCause cause) { 221 if (Math.abs(location.getX()) >= 30000000 || Math.abs(location.getZ()) >= 30000000) { 222 return; 223 } 224 final org.bukkit.Location bukkitLocation = 225 new org.bukkit.Location(BukkitUtil.getWorld(location.getWorldName()), location.getX() + 0.5, 226 location.getY(), location.getZ() + 0.5, location.getYaw(), location.getPitch() 227 ); 228 PaperLib.teleportAsync(player, bukkitLocation, getTeleportCause(cause)); 229 } 230 231 @Override 232 public String getName() { 233 if (this.name == null) { 234 this.name = this.player.getName(); 235 } 236 return this.name; 237 } 238 239 @Override 240 public void setCompassTarget(Location location) { 241 this.player.setCompassTarget( 242 new org.bukkit.Location(BukkitUtil.getWorld(location.getWorldName()), location.getX(), 243 location.getY(), location.getZ() 244 )); 245 } 246 247 @Override 248 public Location getLocationFull() { 249 return BukkitUtil.adaptComplete(this.player.getLocation()); 250 } 251 252 @Override 253 public void setWeather(final @NonNull PlotWeather weather) { 254 switch (weather) { 255 case CLEAR -> this.player.setPlayerWeather(WeatherType.CLEAR); 256 case RAIN -> this.player.setPlayerWeather(WeatherType.DOWNFALL); 257 case WORLD -> this.player.resetPlayerWeather(); 258 default -> { 259 //do nothing as this is PlotWeather.OFF 260 } 261 } 262 } 263 264 @Override 265 public com.sk89q.worldedit.world.gamemode.GameMode getGameMode() { 266 return switch (this.player.getGameMode()) { 267 case ADVENTURE -> ADVENTURE; 268 case CREATIVE -> CREATIVE; 269 case SPECTATOR -> SPECTATOR; 270 default -> SURVIVAL; 271 }; 272 } 273 274 @Override 275 public void setGameMode(final com.sk89q.worldedit.world.gamemode.GameMode gameMode) { 276 if (ADVENTURE.equals(gameMode)) { 277 this.player.setGameMode(GameMode.ADVENTURE); 278 } else if (CREATIVE.equals(gameMode)) { 279 this.player.setGameMode(GameMode.CREATIVE); 280 } else if (SPECTATOR.equals(gameMode)) { 281 this.player.setGameMode(GameMode.SPECTATOR); 282 } else { 283 this.player.setGameMode(GameMode.SURVIVAL); 284 } 285 } 286 287 @Override 288 public void setTime(final long time) { 289 if (time != Long.MAX_VALUE) { 290 this.player.setPlayerTime(time, false); 291 } else { 292 this.player.resetPlayerTime(); 293 } 294 } 295 296 @Override 297 public boolean getFlight() { 298 return player.getAllowFlight(); 299 } 300 301 @Override 302 public void setFlight(boolean fly) { 303 this.player.setAllowFlight(fly); 304 } 305 306 @Override 307 public void playMusic(final @NonNull Location location, final @NonNull ItemType id) { 308 if (id == ItemTypes.AIR) { 309 // Let's just stop all the discs because why not? 310 for (final Sound sound : Arrays.stream(Sound.values()) 311 .filter(sound -> sound.name().contains("DISC")).toList()) { 312 player.stopSound(sound); 313 } 314 // this.player.playEffect(BukkitUtil.getLocation(location), Effect.RECORD_PLAY, Material.AIR); 315 } else { 316 // this.player.playEffect(BukkitUtil.getLocation(location), Effect.RECORD_PLAY, id.to(Material.class)); 317 this.player.playSound(BukkitUtil.adapt(location), 318 Sound.valueOf(BukkitAdapter.adapt(id).name()), Float.MAX_VALUE, 1f 319 ); 320 } 321 } 322 323 @SuppressWarnings("deprecation") // Needed for Spigot compatibility 324 @Override 325 public void kick(final String message) { 326 this.player.kickPlayer(message); 327 } 328 329 @Override 330 public void stopSpectating() { 331 if (getGameMode() == SPECTATOR) { 332 this.player.setSpectatorTarget(null); 333 } 334 } 335 336 @Override 337 public boolean isBanned() { 338 return this.player.isBanned(); 339 } 340 341 @Override 342 public @NonNull Audience getAudience() { 343 return BukkitUtil.BUKKIT_AUDIENCES.player(this.player); 344 } 345 346 @Override 347 public void removeEffect(@NonNull String name) { 348 PotionEffectType type = PotionEffectType.getByName(name); 349 if (type != null) { 350 player.removePotionEffect(type); 351 } 352 } 353 354 @Override 355 public boolean canSee(final PlotPlayer<?> other) { 356 if (other instanceof ConsolePlayer) { 357 return true; 358 } else { 359 return this.player.canSee(((BukkitPlayer) other).getPlatformPlayer()); 360 } 361 } 362 363 /** 364 * Convert from PlotSquared's {@link TeleportCause} to Bukkit's {@link PlayerTeleportEvent.TeleportCause} 365 * 366 * @param cause PlotSquared teleport cause to convert 367 * @return Bukkit's equivalent teleport cause 368 */ 369 public PlayerTeleportEvent.TeleportCause getTeleportCause(final @NonNull TeleportCause cause) { 370 if (TeleportCause.CauseSets.COMMAND.contains(cause)) { 371 return PlayerTeleportEvent.TeleportCause.COMMAND; 372 } else if (cause == TeleportCause.UNKNOWN) { 373 return PlayerTeleportEvent.TeleportCause.UNKNOWN; 374 } 375 return PlayerTeleportEvent.TeleportCause.PLUGIN; 376 } 377 378}