001/* 002 * Trident - A Multithreaded Server Alternative 003 * Copyright 2014 The TridentSDK Team 004 * 005 * Licensed under the Apache License, Version 2.0 (the "License"); 006 * you may not use this file except in compliance with the License. 007 * You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017 018package net.tridentsdk.server.player; 019 020 021import com.google.common.collect.Sets; 022import net.tridentsdk.Trident; 023import net.tridentsdk.bar.BarType; 024import net.tridentsdk.base.Position; 025import net.tridentsdk.entity.Entity; 026import net.tridentsdk.entity.Projectile; 027import net.tridentsdk.entity.living.Player; 028import net.tridentsdk.entity.traits.EntityProperties; 029import net.tridentsdk.entity.traits.PlayerSpeed; 030import net.tridentsdk.event.entity.EntityDamageEvent; 031import net.tridentsdk.inventory.Inventory; 032import net.tridentsdk.inventory.Item; 033import net.tridentsdk.meta.MessageBuilder; 034import net.tridentsdk.meta.nbt.*; 035import net.tridentsdk.registry.Registered; 036import net.tridentsdk.server.TridentServer; 037import net.tridentsdk.server.data.Slot; 038import net.tridentsdk.server.entity.TridentInventoryHolder; 039import net.tridentsdk.server.inventory.TridentInventory; 040import net.tridentsdk.server.world.TridentWorld; 041import net.tridentsdk.title.TitleTransition; 042import net.tridentsdk.util.TridentLogger; 043import net.tridentsdk.world.World; 044import net.tridentsdk.world.settings.Dimension; 045import net.tridentsdk.world.settings.GameMode; 046 047import javax.annotation.concurrent.ThreadSafe; 048import java.util.Locale; 049import java.util.Map; 050import java.util.Set; 051import java.util.UUID; 052import java.util.concurrent.ConcurrentHashMap; 053 054@ThreadSafe 055public class OfflinePlayer extends TridentInventoryHolder implements Player { 056 static final Map<UUID, OfflinePlayer> OFFLINE_PLAYERS = new ConcurrentHashMap<>(); 057 058 /** 059 * The name of the player 060 */ 061 protected volatile String name; 062 /** 063 * The dimension of the player 064 */ 065 protected volatile Dimension dimension; 066 /** 067 * The gamemode the player is currently in 068 */ 069 protected volatile GameMode gameMode; 070 /** 071 * TODO 072 */ 073 protected volatile int score; 074 /** 075 * The current slot selected by the player 076 */ 077 protected volatile short selectedSlot; 078 /** 079 * The spawn location of the player 080 */ 081 protected volatile Position spawnPosition; 082 /** 083 * The current hunger of the player 084 */ 085 protected volatile short hunger; 086 /** 087 * The exaustion of the player 088 */ 089 protected volatile float exhaustion; 090 /** 091 * The current food saturation of the player 092 */ 093 protected volatile float saturation; 094 /** 095 * The next ticks that will be run before the player drops in hunger 096 */ 097 protected volatile int foodTickTimer; 098 /** 099 * The player's experience level 100 */ 101 protected volatile int xpLevel; 102 /** 103 * The percentage of experience currently in the level 104 */ 105 protected volatile float xpPercent; 106 /** 107 * The total numerical experience the player has 108 */ 109 protected volatile int xpTotal; 110 /** 111 * The experience seed of the player 112 */ 113 protected volatile int xpSeed; 114 115 protected final Inventory enderChest = null; 116 protected final PlayerAbilities abilities = new PlayerAbilities(); 117 protected final PlayerSpeed playerSpeed = new PlayerSpeedImpl(); 118 protected final Set<String> permissions = Sets.newConcurrentHashSet(); 119 120 OfflinePlayer(UUID uuid, CompoundTag tag, TridentWorld world) { 121 super(uuid, world.spawnPosition()); 122 123 load(tag); 124 125 dimension = Dimension.of(((IntTag) tag.getTag("Dimension")).value()); 126 gameMode = GameMode.of(((IntTag) tag.getTag("playerGameType")).value()); 127 score = ((IntTag) tag.getTag("Score")).value(); 128 selectedSlot = (short) ((IntTag) tag.getTag("SelectedItemSlot")).value(); 129 130 if (tag.containsTag("SpawnX")) { 131 spawnPosition = Position.create(world, ((IntTag) tag.getTag("SpawnX")).value(), 132 ((IntTag) tag.getTag("SpawnY")).value(), ((IntTag) tag.getTag("SpawnZ")).value()); 133 } else { 134 spawnPosition = world.spawnPosition(); 135 } 136 137 hunger = (short) ((IntTag) tag.getTag("foodLevel")).value(); 138 exhaustion = ((FloatTag) tag.getTag("foodExhaustionLevel")).value(); 139 saturation = ((FloatTag) tag.getTag("foodSaturationLevel")).value(); 140 foodTickTimer = ((IntTag) tag.getTag("foodTickTimer")).value(); 141 xpLevel = ((IntTag) tag.getTag("XpLevel")).value(); 142 xpPercent = ((FloatTag) tag.getTag("XpP")).value(); 143 xpTotal = ((IntTag) tag.getTag("XpLevel")).value(); 144 xpSeed = tag.containsTag("XpSeed") ? ((IntTag) tag.getTag("XpSeed")).value() : 145 new IntTag("XpSeed").setValue(0).value(); 146 147 // TODO come up with a valid implementation of this...? 148 inventory = new TridentInventory(45, 0); 149 for (NBTTag t : ((ListTag) tag.getTag("Inventory")).listTags()) { 150 Slot slot = NBTSerializer.deserialize(Slot.class, (CompoundTag) t); 151 152 //inventory.setSlot(slot.getSlot(), slot.toItemStack()); 153 } 154 155 for (NBTTag t : ((ListTag) tag.getTag("EnderItems")).listTags()) { 156 Slot slot = NBTSerializer.deserialize(Slot.class, (CompoundTag) t); 157 158 //enderChest.setSlot(slot.getSlot(), slot.toItemStack()); 159 } 160 161 NBTSerializer.deserialize(abilities, (CompoundTag) tag.getTag("abilities")); 162 } 163 164 public static OfflinePlayer getOfflinePlayer(UUID id) { 165 return OFFLINE_PLAYERS.get(id); 166 } 167 168 public static CompoundTag generatePlayer(UUID id) { 169 // DEBUG ===== 170 World defaultWorld = TridentServer.WORLD; 171 // ===== 172 Position spawnLocation = defaultWorld.spawnPosition(); 173 CompoundTagBuilder<NBTBuilder> builder = NBTBuilder.newBase(id.toString()); 174 175 builder.stringTag("id", String.valueOf(counter.incrementAndGet())); 176 builder.longTag("UUIDMost", id.getMostSignificantBits()); 177 builder.longTag("UUIDLeast", id.getLeastSignificantBits()); 178 179 ListTagBuilder<CompoundTagBuilder<NBTBuilder>> pos = builder.beginListTag("Pos", TagType.DOUBLE); 180 181 pos.tag(spawnLocation.x()); 182 pos.tag(spawnLocation.y()); 183 pos.tag(spawnLocation.z()); 184 185 builder = pos.endListTag(); 186 187 ListTagBuilder<CompoundTagBuilder<NBTBuilder>> motion = builder.beginListTag("Motion", TagType.DOUBLE); 188 189 motion.tag(0d); 190 motion.tag(0d); 191 motion.tag(0d); 192 193 builder = motion.endListTag(); 194 195 ListTagBuilder<CompoundTagBuilder<NBTBuilder>> rotation = builder.beginListTag("Rotation", TagType.FLOAT); 196 197 rotation.tag(0f); 198 rotation.tag(0f); 199 200 builder = rotation.endListTag(); 201 202 builder.floatTag("FallDistance", 0); 203 builder.shortTag("Fire", (short) -20); 204 builder.shortTag("Air", (short) 0); 205 206 builder.byteTag("OnGround", (byte) 1); 207 builder.byteTag("Invulnerable", (byte) 0); 208 209 builder.intTag("Dimension", Dimension.OVERWORLD.asByte()); 210 builder.intTag("PortalCooldown", 900); 211 212 builder.stringTag("CustomName", ""); 213 // does not apply to onlinePlayers 214 //builder.byteTag("CustomNameVisible", (byte) 0); 215 216 builder.byteTag("Silent", (byte) 0); 217 218 builder.compoundTag(new CompoundTag("Riding")); 219 220 builder.intTag("Dimension", Dimension.OVERWORLD.asByte()); 221 builder.intTag("playerGameType", Trident.config().getByte("default-gamemode")); 222 builder.intTag("Score", 0); 223 builder.intTag("SelectedGameSlot", 0); 224 225 builder.intTag("foodLevel", 20); 226 builder.floatTag("foodExhaustionLevel", 0F); 227 builder.floatTag("foodSaturationLevel", 0F); 228 builder.intTag("foodTickTimer", 0); 229 230 builder.intTag("XpLevel", 0); 231 builder.floatTag("XpP", 0); 232 builder.intTag("XpLevel", 0); 233 builder.intTag("XpSeed", 0); // actually give a proper seed 234 235 builder.listTag(new ListTag("Inventory", TagType.COMPOUND)); 236 builder.listTag(new ListTag("EnderItems", TagType.COMPOUND)); 237 238 builder.intTag("SelectedItemSlot", 0); 239 240 builder.compoundTag(NBTSerializer.serialize(new PlayerAbilities(), "abilities")); 241 242 return builder.endCompoundTag().build(); 243 } 244 245 public Position spawnLocation() { 246 return spawnPosition; 247 } 248 249 @Override 250 public Locale locale() { 251 return null; 252 } 253 254 @Override 255 public GameMode gameMode() { 256 return gameMode; 257 } 258 259 @Override 260 public void setGameMode(GameMode mode) { 261 this.gameMode = mode; 262 this.abilities.creative = (byte) ((mode == GameMode.CREATIVE) ? 1 : 0); 263 this.abilities.canFly = (byte) ((mode == GameMode.CREATIVE) ? 1 : 0); 264 } 265 266 @Override 267 public PlayerSpeed speedModifiers() { 268 return playerSpeed; 269 } 270 271 @Override 272 public void sendMessage(String message) { 273 TridentLogger.get().error(new UnsupportedOperationException("You can't send messages to a non-existant player")); 274 } 275 276 @Override 277 public boolean connected() { 278 return false; 279 } 280 281 @Override 282 public void invokeCommand(String message) { 283 TridentLogger.get().error(new UnsupportedOperationException("You cannot make an OfflinePlayer invoke a command!")); 284 } 285 286 @Override 287 public String lastCommand() { 288 return null; 289 } 290 291 @Override 292 public Player asPlayer() { 293 return null; 294 } 295 296 @Override 297 public void hide(Entity entity) { 298 TridentLogger.get().error(new UnsupportedOperationException("You cannot hide an entity from an OfflinePlayer!")); 299 } 300 301 @Override 302 public void show(Entity entity) { 303 TridentLogger.get().error(new UnsupportedOperationException("You cannot show an entity to an OfflinePlayer!")); 304 } 305 306 @Override 307 public EntityDamageEvent lastDamageEvent() { 308 return null; 309 } 310 311 @Override 312 public Player lastPlayerDamager() { 313 return null; 314 } 315 316 @Override 317 public void sendRaw(String... messages) { 318 TridentLogger.get().error(new UnsupportedOperationException("You cannot send a message to an OfflinePlayer!")); 319 } 320 321 @Override 322 public String lastMessage() { 323 return null; 324 } 325 326 @Override 327 public String name() { 328 return name; 329 } 330 331 @Override 332 public <T extends Projectile> T launchProjectile(EntityProperties properties) { 333 TridentLogger.get().error(new UnsupportedOperationException("You cannot make an OfflinePlayer launch a projectile!")); 334 return null; 335 } 336 337 public CompoundTag asNbt() { 338 CompoundTag tag = new CompoundTag(uniqueId().toString()); 339 340 tag.addTag(new LongTag("UUIDMost").setValue(uniqueId.getMostSignificantBits())); 341 tag.addTag(new LongTag("UUIDLeast").setValue(uniqueId.getLeastSignificantBits())); 342 343 tag.addTag(new IntTag("Dimension").setValue(dimension.asByte())); 344 tag.addTag(new IntTag("playerGameType").setValue(gameMode.asByte())); 345 tag.addTag(new IntTag("Score").setValue(score)); 346 tag.addTag(new IntTag("SelectedItemSlot").setValue(selectedSlot)); 347 348 //tag.addTag(NBTSerializer.serialize(new Slot(itemInHand()))); 349 tag.addTag(new IntTag("SpawnX").setValue((int) spawnPosition.x())); 350 tag.addTag(new IntTag("SpawnY").setValue((int) spawnPosition.y())); 351 tag.addTag(new IntTag("SpawnZ").setValue((int) spawnPosition.z())); 352 353 tag.addTag(new IntTag("foodLevel").setValue(hunger)); 354 tag.addTag(new FloatTag("foodExhaustionLevel").setValue(exhaustion)); 355 tag.addTag(new FloatTag("foodSaturationLevel").setValue(saturation)); 356 tag.addTag(new IntTag("foodTickTimer").setValue(foodTickTimer)); 357 358 tag.addTag(new IntTag("XpLevel").setValue(xpLevel)); 359 tag.addTag(new FloatTag("XpP").setValue(xpPercent)); 360 tag.addTag(new IntTag("XpTotal").setValue(xpTotal)); 361 tag.addTag(new IntTag("XpSeed").setValue(xpSeed)); 362 363 tag.addTag(new ByteTag("Invulnerable").setValue(godMode)); 364 tag.addTag(new IntTag("PortalCooldown").setValue(portalCooldown.get())); 365 tag.addTag(new FloatTag("FallDistance").setValue(fallDistance.floatValue())); 366 tag.addTag(new ByteTag("OnGround").setValue(onGround)); 367 tag.addTag(new ShortTag("Fire").setValue(fireTicks.shortValue())); 368 tag.addTag(new ShortTag("Air").setValue((short) airTicks.get())); 369 tag.addTag(new ByteTag("Silent").setValue(silent)); 370 tag.addTag(new IntTag("SelectedItemSlot").setValue(selectedSlot)); 371 372 ListTag position = new ListTag("Pos",TagType.DOUBLE); 373 position.addTag(new DoubleTag("").setValue(loc.x())); 374 position.addTag(new DoubleTag("").setValue(loc.y())); 375 position.addTag(new DoubleTag("").setValue(loc.z())); 376 377 tag.addTag(position); 378 379 ListTag motion = new ListTag("Motion",TagType.DOUBLE) ; 380 motion.addTag(new DoubleTag("").setValue(velocity.x())); 381 motion.addTag(new DoubleTag("").setValue(velocity.y())); 382 motion.addTag(new DoubleTag("").setValue(velocity.z())); 383 384 tag.addTag(motion); 385 386 ListTag rotation = new ListTag("Rotation", TagType.FLOAT); 387 rotation.addTag(new FloatTag("").setValue(loc.yaw())); 388 rotation.addTag(new FloatTag("").setValue(loc.pitch())); 389 390 tag.addTag(rotation); 391 392 ListTag inventoryTag = new ListTag("Inventory", TagType.COMPOUND); 393 394 /*for (ItemStack is : inventory.items()) { 395 inventoryTag.addTag(NBTSerializer.serialize(new Slot(is))); 396 }*/ 397 398 tag.addTag(inventoryTag); 399 400 ListTag enderTag = new ListTag("EnderItems", TagType.COMPOUND); 401 402 /*for (ItemStack is : enderChest.items()) { 403 enderTag.addTag(NBTSerializer.serialize(new Slot(is))); 404 }*/ 405 406 tag.addTag(enderTag); 407 tag.addTag(NBTSerializer.serialize(abilities, "abilities")); 408 409 return tag; 410 } 411 412 @Override 413 public void grantPermission(String perm) { 414 permissions.add(perm); 415 } 416 417 @Override 418 public void revokePermission(String perm) { 419 permissions.remove(perm); 420 } 421 422 @Override 423 public boolean ownsPermission(String perm) { 424 return perm.isEmpty() || opped() || permissions.contains(perm); 425 } 426 427 @Override 428 public boolean opped() { 429 return Registered.statuses().isOpped(uniqueId()); 430 } 431 432 @Override 433 public void sendBar(BarType barType, String s) { 434 TridentLogger.get().error(new UnsupportedOperationException("You may not send a bar to an OfflinePlayer!")); 435 } 436 437 @Override 438 public void sendTitle(String s) { 439 TridentLogger.get().error(new UnsupportedOperationException("You may not send a title to an OfflinePlayer!")); 440 } 441 442 @Override 443 public void sendTitle(String s, String s1) { 444 TridentLogger.get().error(new UnsupportedOperationException("You may not send a title to an OfflinePlayer!")); 445 } 446 447 @Override 448 public void sendTitle(String s, TitleTransition titleTransition) { 449 TridentLogger.get().error(new UnsupportedOperationException("You may not send a title to an OfflinePlayer!")); 450 } 451 452 @Override 453 public void sendTitle(String s, String s1, TitleTransition titleTransition) { 454 TridentLogger.get().error(new UnsupportedOperationException("You may not send a title to an OfflinePlayer!")); 455 } 456 457 class PlayerSpeedImpl implements PlayerSpeed { 458 @Override 459 public float flyingSpeed() { 460 return abilities.flySpeed; 461 } 462 463 @Override 464 public void setFlyingSpeed(float flyingSpeed) { 465 abilities.flySpeed = flyingSpeed; 466 } 467 468 @Override 469 public float sneakSpeed() { 470 TridentLogger.get().error(new UnsupportedOperationException("You may not get the sneak speed of an OfflinePlayer!")); 471 return -1; 472 } 473 474 @Override 475 public void setSneakSpeed(float speed) { 476 TridentLogger.get().error(new UnsupportedOperationException("You may not set the sneak speed of an OfflinePlayer!")); 477 } 478 479 @Override 480 public float walkSpeed() { 481 return abilities.walkingSpeed; 482 } 483 484 @Override 485 public void setWalkSpeed(float speed) { 486 abilities.walkingSpeed = speed; 487 } 488 } 489 490 @Override 491 public Item pickedItem() { 492 TridentLogger.get().error(new UnsupportedOperationException("You may not get the cursor item of an OfflinePlayer!")); 493 return null; 494 } 495 496 @Override 497 public void setPickedItem(Item item) { 498 TridentLogger.get().error(new UnsupportedOperationException("You may not set the cursor item of an OfflinePlayer!")); 499 } 500 501 @Override 502 public String header() { 503 return null; 504 } 505 506 @Override 507 public void setHeader(MessageBuilder builder) { 508 TridentLogger.get().error(new UnsupportedOperationException("You cannot set the header of an OfflinePlayer!")); 509 } 510 511 @Override 512 public String footer() { 513 return null; 514 } 515 516 @Override 517 public void setFooter(MessageBuilder builder) { 518 TridentLogger.get().error(new UnsupportedOperationException("You cannot set the footer of an OfflinePlayer!")); 519 } 520}