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