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 // Wildcard check from less specific permission to more specific permission 162 for (int i = 0; i < (nodes.length - 1); i++) { 163 n.append(nodes[i]).append("."); 164 if (!stub.equals(n + Permission.PERMISSION_STAR.toString())) { 165 if (hasPermission(n + Permission.PERMISSION_STAR.toString())) { 166 return Integer.MAX_VALUE; 167 } 168 } 169 } 170 // Wildcard check for the full permission 171 if (hasPermission(stub + ".*")) { 172 return Integer.MAX_VALUE; 173 } 174 // Permission value cache for iterative check 175 int max = 0; 176 if (CHECK_EFFECTIVE) { 177 boolean hasAny = false; 178 String stubPlus = stub + "."; 179 final Set<PermissionAttachmentInfo> effective = player.getEffectivePermissions(); 180 if (!effective.isEmpty()) { 181 for (PermissionAttachmentInfo attach : effective) { 182 // Ignore all "false" permissions 183 if (!attach.getValue()) { 184 continue; 185 } 186 String permStr = attach.getPermission(); 187 if (permStr.startsWith(stubPlus)) { 188 hasAny = true; 189 String end = permStr.substring(stubPlus.length()); 190 if (MathMan.isInteger(end)) { 191 int val = Integer.parseInt(end); 192 if (val > range) { 193 return val; 194 } 195 if (val > max) { 196 max = val; 197 } 198 } 199 } 200 } 201 if (hasAny) { 202 return max; 203 } 204 // Workaround 205 for (PermissionAttachmentInfo attach : effective) { 206 String permStr = attach.getPermission(); 207 if (permStr.startsWith("plots.") && !permStr.equals("plots.use")) { 208 return max; 209 } 210 } 211 CHECK_EFFECTIVE = false; 212 } 213 } 214 for (int i = range; i > 0; i--) { 215 if (hasPermission(stub + "." + i)) { 216 return i; 217 } 218 } 219 return max; 220 } 221 222 @Override 223 public void teleport(final @NonNull Location location, final @NonNull TeleportCause cause) { 224 if (Math.abs(location.getX()) >= 30000000 || Math.abs(location.getZ()) >= 30000000) { 225 return; 226 } 227 final org.bukkit.Location bukkitLocation = 228 new org.bukkit.Location(BukkitUtil.getWorld(location.getWorldName()), location.getX() + 0.5, 229 location.getY(), location.getZ() + 0.5, location.getYaw(), location.getPitch() 230 ); 231 PaperLib.teleportAsync(player, bukkitLocation, getTeleportCause(cause)); 232 } 233 234 @Override 235 public String getName() { 236 if (this.name == null) { 237 this.name = this.player.getName(); 238 } 239 return this.name; 240 } 241 242 @Override 243 public void setCompassTarget(Location location) { 244 this.player.setCompassTarget( 245 new org.bukkit.Location(BukkitUtil.getWorld(location.getWorldName()), location.getX(), 246 location.getY(), location.getZ() 247 )); 248 } 249 250 @Override 251 public Location getLocationFull() { 252 return BukkitUtil.adaptComplete(this.player.getLocation()); 253 } 254 255 @Override 256 public void setWeather(final @NonNull PlotWeather weather) { 257 switch (weather) { 258 case CLEAR -> this.player.setPlayerWeather(WeatherType.CLEAR); 259 case RAIN -> this.player.setPlayerWeather(WeatherType.DOWNFALL); 260 case WORLD -> this.player.resetPlayerWeather(); 261 default -> { 262 //do nothing as this is PlotWeather.OFF 263 } 264 } 265 } 266 267 @Override 268 public com.sk89q.worldedit.world.gamemode.GameMode getGameMode() { 269 return switch (this.player.getGameMode()) { 270 case ADVENTURE -> ADVENTURE; 271 case CREATIVE -> CREATIVE; 272 case SPECTATOR -> SPECTATOR; 273 default -> SURVIVAL; 274 }; 275 } 276 277 @Override 278 public void setGameMode(final com.sk89q.worldedit.world.gamemode.GameMode gameMode) { 279 if (ADVENTURE.equals(gameMode)) { 280 this.player.setGameMode(GameMode.ADVENTURE); 281 } else if (CREATIVE.equals(gameMode)) { 282 this.player.setGameMode(GameMode.CREATIVE); 283 } else if (SPECTATOR.equals(gameMode)) { 284 this.player.setGameMode(GameMode.SPECTATOR); 285 } else { 286 this.player.setGameMode(GameMode.SURVIVAL); 287 } 288 } 289 290 @Override 291 public void setTime(final long time) { 292 if (time != Long.MAX_VALUE) { 293 this.player.setPlayerTime(time, false); 294 } else { 295 this.player.resetPlayerTime(); 296 } 297 } 298 299 @Override 300 public boolean getFlight() { 301 return player.getAllowFlight(); 302 } 303 304 @Override 305 public void setFlight(boolean fly) { 306 this.player.setAllowFlight(fly); 307 } 308 309 @Override 310 public void playMusic(final @NonNull Location location, final @NonNull ItemType id) { 311 if (id == ItemTypes.AIR) { 312 // Let's just stop all the discs because why not? 313 for (final Sound sound : Arrays.stream(Sound.values()) 314 .filter(sound -> sound.name().contains("DISC")).toList()) { 315 player.stopSound(sound); 316 } 317 // this.player.playEffect(BukkitUtil.getLocation(location), Effect.RECORD_PLAY, Material.AIR); 318 } else { 319 // this.player.playEffect(BukkitUtil.getLocation(location), Effect.RECORD_PLAY, id.to(Material.class)); 320 this.player.playSound(BukkitUtil.adapt(location), 321 Sound.valueOf(BukkitAdapter.adapt(id).name()), Float.MAX_VALUE, 1f 322 ); 323 } 324 } 325 326 @SuppressWarnings("deprecation") // Needed for Spigot compatibility 327 @Override 328 public void kick(final String message) { 329 this.player.kickPlayer(message); 330 } 331 332 @Override 333 public void stopSpectating() { 334 if (getGameMode() == SPECTATOR) { 335 this.player.setSpectatorTarget(null); 336 } 337 } 338 339 @Override 340 public boolean isBanned() { 341 return this.player.isBanned(); 342 } 343 344 @Override 345 public @NonNull Audience getAudience() { 346 return BukkitUtil.BUKKIT_AUDIENCES.player(this.player); 347 } 348 349 @Override 350 public void removeEffect(@NonNull String name) { 351 PotionEffectType type = PotionEffectType.getByName(name); 352 if (type != null) { 353 player.removePotionEffect(type); 354 } 355 } 356 357 @Override 358 public boolean canSee(final PlotPlayer<?> other) { 359 if (other instanceof ConsolePlayer) { 360 return true; 361 } else { 362 return this.player.canSee(((BukkitPlayer) other).getPlatformPlayer()); 363 } 364 } 365 366 /** 367 * Convert from PlotSquared's {@link TeleportCause} to Bukkit's {@link PlayerTeleportEvent.TeleportCause} 368 * 369 * @param cause PlotSquared teleport cause to convert 370 * @return Bukkit's equivalent teleport cause 371 */ 372 public PlayerTeleportEvent.TeleportCause getTeleportCause(final @NonNull TeleportCause cause) { 373 if (TeleportCause.CauseSets.COMMAND.contains(cause)) { 374 return PlayerTeleportEvent.TeleportCause.COMMAND; 375 } else if (cause == TeleportCause.UNKNOWN) { 376 return PlayerTeleportEvent.TeleportCause.UNKNOWN; 377 } 378 return PlayerTeleportEvent.TeleportCause.PLUGIN; 379 } 380 381}