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