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.entity; 019 020import com.google.common.util.concurrent.AtomicDouble; 021import net.tridentsdk.base.BoundingBox; 022import net.tridentsdk.base.Position; 023import net.tridentsdk.base.Substance; 024import net.tridentsdk.concurrent.SelectableThreadPool; 025import net.tridentsdk.docs.InternalUseOnly; 026import net.tridentsdk.docs.PossiblyThreadSafe; 027import net.tridentsdk.entity.Entity; 028import net.tridentsdk.entity.traits.EntityProperties; 029import net.tridentsdk.entity.types.EntityType; 030import net.tridentsdk.meta.nbt.*; 031import net.tridentsdk.server.TridentServer; 032import net.tridentsdk.server.concurrent.ThreadsHandler; 033import net.tridentsdk.server.concurrent.TickSync; 034import net.tridentsdk.server.data.MetadataType; 035import net.tridentsdk.server.data.ProtocolMetadata; 036import net.tridentsdk.server.packets.play.out.PacketPlayOutDestroyEntities; 037import net.tridentsdk.server.packets.play.out.PacketPlayOutEntityTeleport; 038import net.tridentsdk.server.packets.play.out.PacketPlayOutEntityVelocity; 039import net.tridentsdk.server.player.TridentPlayer; 040import net.tridentsdk.server.world.TridentChunk; 041import net.tridentsdk.server.world.TridentWorld; 042import net.tridentsdk.util.Vector; 043import net.tridentsdk.util.WeakEntity; 044import net.tridentsdk.world.World; 045 046import java.util.List; 047import java.util.Set; 048import java.util.UUID; 049import java.util.concurrent.atomic.AtomicInteger; 050import java.util.concurrent.atomic.AtomicLong; 051import java.util.stream.Collectors; 052 053/** 054 * Entity abstraction base 055 * 056 * @author The TridentSDK Team 057 */ 058@PossiblyThreadSafe 059public class TridentEntity implements Entity { 060 @InternalUseOnly 061 protected static final AtomicInteger counter = new AtomicInteger(-1); 062 063 /** 064 * Internal entity tracker, used to spawn the entity and track movement, etc. 065 */ 066 public static final EntityHandler HANDLER = EntityHandler.create(); 067 /** 068 * The distance the entity has fallen 069 */ 070 protected final AtomicDouble fallDistance = new AtomicDouble(0L); 071 /** 072 * The ticks that have passed since the entity was spawned, and alive 073 */ 074 protected final AtomicLong ticksExisted = new AtomicLong(0L); 075 /** 076 * How long the entity has been on fire 077 */ 078 protected final AtomicInteger fireTicks = new AtomicInteger(0); 079 /** 080 * How many ticks of air the entity has left 081 */ 082 protected final AtomicLong airTicks = new AtomicLong(); 083 /** 084 * Length of time the entity must wait to enter a portal. Unknown unit. TODO 085 */ 086 protected final AtomicInteger portalCooldown = new AtomicInteger(900); 087 /** 088 * The entity ID for the entity 089 */ 090 protected volatile int id; 091 /** 092 * The identifier UUID for the entity 093 */ 094 protected volatile UUID uniqueId; 095 /** 096 * Entity task executor 097 */ 098 protected volatile SelectableThreadPool executor = ThreadsHandler.entityExecutor(); 099 /** 100 * The movement vector for the entity 101 */ 102 protected volatile Vector velocity; 103 /** 104 * The entity location 105 */ 106 protected volatile Position loc; 107 /** 108 * Whether or not the entity is touching the ground 109 */ 110 protected volatile boolean onGround; 111 /** 112 * The entity's passenger, if there are any 113 */ 114 protected volatile Entity passenger; 115 /** 116 * The name of the entity appearing above the head 117 */ 118 protected volatile String displayName; 119 /** 120 * Whether or not the name of the entity is visible 121 */ 122 protected volatile boolean nameVisible; 123 /** 124 * TODO 125 */ 126 protected volatile boolean silent; 127 /** 128 * {@code true} to indicate the entity cannot be damaged 129 */ 130 protected volatile boolean godMode; 131 /** 132 * TODO 133 */ 134 protected volatile BoundingBox boundingBox; 135 /** 136 * TODO 137 */ 138 protected volatile float width; 139 /** 140 * TODO 141 */ 142 protected volatile float height; 143 144 /** 145 * Creates a new entity 146 * 147 * @param uniqueId the UUID of the entity 148 * @param spawnLocation the location which the entity is to be spawned 149 */ 150 public TridentEntity(UUID uniqueId, Position spawnLocation) { 151 this.uniqueId = uniqueId; 152 this.id = counter.incrementAndGet(); 153 this.velocity = new Vector(0.0D, 0.0D, 0.0D); 154 this.loc = spawnLocation; 155 this.boundingBox = new BoundingBox(0.0D, 0.0D, 0.0D, 0.0D, 0.0D, 0.0D); 156 setSize(0.6f, 1.8f); 157 updateBoudingBox(); 158 for (double y = this.loc.y(); y > 0.0; y--) { 159 Position l = Position.create(this.loc.world(), this.loc.x(), y, this.loc.z()); 160 161 if (l.block().substance() != Substance.AIR) { 162 this.fallDistance.set((long) (this.loc.y() - y)); 163 this.onGround = this.fallDistance.get() == 0.0D; 164 165 break; 166 } 167 } 168 } 169 170 @Deprecated 171 protected TridentEntity() { 172 // constructor for deserializing 173 } 174 175 protected void doTick() { 176 } 177 178 protected void doRemove() { 179 } 180 181 protected void doEncodeMeta(ProtocolMetadata protocolMeta) { 182 } 183 184 protected void doLoad(CompoundTag tag) { 185 } 186 187 /** 188 * Begin entity management 189 * 190 * @return the current entity 191 */ 192 public TridentEntity spawn() { 193 HANDLER.register(this); 194 ((TridentChunk) loc.chunk()).entitiesInternal().add(this); 195 ((TridentWorld) loc.world()).addEntity(this); 196 return this; 197 } 198 199 protected void encodeMetadata(ProtocolMetadata protocolMeta) { 200 protocolMeta.setMeta(0, MetadataType.BYTE, (byte) ((fireTicks.intValue() == 0) ? 0 : 1)); 201 protocolMeta.setMeta(1, MetadataType.SHORT, airTicks.shortValue()); 202 doEncodeMeta(protocolMeta); 203 } 204 205 @Override 206 public void teleport(double x, double y, double z) { 207 this.teleport(Position.create(this.world(), x, y, z)); 208 } 209 210 @Override 211 public void teleport(Entity entity) { 212 this.teleport(entity.position()); 213 } 214 215 @Override 216 public void teleport(Position location) { 217 this.loc = location; 218 219 for (double y = this.loc.y(); y > 0.0; y--) { 220 Position l = Position.create(this.loc.world(), this.loc.x(), y, this.loc.z()); 221 222 if (l.world().blockAt(l).substance() != Substance.AIR) { 223 this.fallDistance.set((long) (this.loc.y() - y)); 224 this.onGround = this.fallDistance.get() == 0.0D; 225 226 break; 227 } 228 } 229 230 TridentPlayer.sendAll(new PacketPlayOutEntityTeleport().set("entityId", this.id) 231 .set("location", this.loc) 232 .set("onGround", this.onGround)); 233 } 234 235 @Override 236 public World world() { 237 return this.loc.world(); 238 } 239 240 @Override 241 public Position position() { 242 return this.loc; 243 } 244 245 public void setPosition(Position loc) { 246 TridentChunk from = (TridentChunk) position().chunk(); 247 TridentChunk chunk = (TridentChunk) loc.chunk(); 248 if (!from.equals(chunk)) { 249 from.entitiesInternal().remove(this); 250 chunk.entitiesInternal().add(this); 251 } 252 253 this.loc = loc; 254 updateBoudingBox(); 255 } 256 257 private void updateBoudingBox(){ 258 double halfWidth = this.width / 2.0F; 259 this.boundingBox = new BoundingBox(loc.x() - halfWidth, loc.y(), loc.z() - halfWidth, loc.x() + halfWidth, loc.y() + this.height, loc.z() + halfWidth); 260 } 261 262 @Override 263 public Vector velocity() { 264 return this.velocity; 265 } 266 267 @Override 268 public void setVelocity(Vector vector) { 269 this.velocity = vector; 270 271 TridentPlayer.sendAll(new PacketPlayOutEntityVelocity().set("entityId", this.id).set("velocity", vector)); 272 } 273 274 @Override 275 public String displayName() { 276 return this.displayName; 277 } 278 279 @Override 280 public void setDisplayName(String name) { 281 this.displayName = name; 282 } 283 284 @Override 285 public boolean isSilent() { 286 return this.silent; 287 } 288 289 @Override 290 public UUID uniqueId() { 291 return this.uniqueId; 292 } 293 294 public void tick() { 295 executor.execute(() -> { 296 ticksExisted.incrementAndGet(); 297 doTick(); 298 if (ticksExisted.get() % 20 == 0) { 299 updateBoudingBox(); 300 } 301 TickSync.complete("ENTITY: uuid-" + uniqueId.toString() + " id-" + id); 302 }); 303 } 304 305 @Override 306 public boolean onGround() { 307 return this.onGround; 308 } 309 310 public void setOnGround(boolean onGround) { 311 this.onGround = onGround; 312 } 313 314 @Override 315 public Set<Entity> withinRange(double radius) { 316 double squared = radius * radius; 317 Set<Entity> entities = position().world().entities(); 318 319 return entities.stream() 320 .filter((e) -> e.position().distanceSquared(position()) <= squared) 321 .collect(Collectors.toSet()); 322 } 323 324 @Override 325 public int entityId() { 326 return this.id; 327 } 328 329 @Override 330 public void remove() { 331 PacketPlayOutDestroyEntities packet = new PacketPlayOutDestroyEntities(); 332 packet.set("destroyedEntities", new int[] { entityId() }); 333 TridentPlayer.sendAll(packet); 334 HANDLER.removeEntity(this); 335 ((TridentWorld) world()).removeEntity(this); 336 337 try { 338 WeakEntity.clearReferencesTo(this); 339 } catch (IllegalAccessException e) { 340 e.printStackTrace(); 341 } 342 343 doRemove(); 344 } 345 346 @Override 347 public Entity passenger() { 348 return this.passenger; 349 } 350 351 @Override 352 public void setPassenger(Entity entity) { 353 this.passenger = entity; 354 355 // TODO: Update clients 356 } 357 358 @Override 359 public void eject() { 360 // TODO 361 } 362 363 @Override 364 public EntityType type() { 365 return null; 366 } 367 368 @Override 369 public boolean isNameVisible() { 370 return nameVisible; 371 } 372 373 @Override 374 public void applyProperties(EntityProperties properties) { 375 } 376 377 public void load(CompoundTag tag) { 378 /* IDs */ 379 if(!(tag.getTag("id") instanceof NullTag)) { 380 // onlinePlayers will not have this value 381 String type = ((StringTag) tag.getTag("id")).value(); // EntityType, in form of a string 382 } 383 LongTag uuidMost = tag.getTagAs("UUIDMost"); // most signifigant bits of UUID 384 LongTag uuidLeast = tag.getTagAs("UUIDLeast"); // least signifigant bits of UUID 385 386 /* Location and Velocity */ 387 List<NBTTag> pos = ((ListTag) tag.getTagAs("Pos")).listTags(); // 3 double tags describing x, y, z 388 List<NBTTag> motion = ((ListTag) tag.getTagAs("Motion")).listTags(); // 3 double tags describing velocity 389 List<NBTTag> rotation = ((ListTag) tag.getTagAs( 390 "Rotation")).listTags(); // 2 float tags describing yaw and pitch 391 392 FloatTag fallDistance = tag.getTagAs("FallDistance"); // distance from the entity to the ground 393 ShortTag fireTicks = tag.getTagAs("Fire"); // number of ticks until fire goes out 394 ShortTag airTicks = tag.getTagAs("Air"); // how much air the entity has, in ticks. Tag is inverted for squids 395 396 ByteTag onGround = tag.getTagAs("OnGround"); // 0 = false, 1 = true - True if entity is on the ground 397 ByteTag invulnerable = tag.getTagAs("Invulnerable"); // 0 = false, 1 = true If god mode is enabled, essentially. 398 399 /* Dimensions */ 400 IntTag dimension = tag.getTagAs("Dimension"); // no found usage; -1 for nether, 0 for overworld, 1 for end 401 IntTag portalCooldown = tag.getTagAs( 402 "PortalCooldown"); // amount of ticks until entity can use a portal, starts at 900 403 404 /* Display Name */ 405 StringTag displayName = (tag.containsTag("CustomName")) ? (StringTag) tag.getTag("CustomName") : new StringTag( 406 "CustomName").setValue(""); // Custom name for the entity, other known as display name. 407 ByteTag dnVisible = (tag.containsTag("CustomNameVisible")) ? (ByteTag) tag.getTag( 408 "CustomNameVisible") : new ByteTag("CustomNameVisible").setValue( 409 (byte) 0); // 0 = false, 1 = true - If true, it will always appear above them 410 411 ByteTag silent = (tag.containsTag("Silent")) ? (ByteTag) tag.getTag("Silent") : new ByteTag("Silent").setValue( 412 (byte) 0); // 0 = false, 1 = true - If true, the entity will not make a sound 413 414 NBTTag riding = tag.getTagAs("Riding"); // CompoundTag of the entity being ridden, contents are recursive 415 NBTTag commandStats = tag.getTagAs("CommandStats"); // Information to modify relative to the last command run 416 417 /* Set data */ 418 this.id = counter.incrementAndGet(); 419 420 // TODO this is temporary for testing 421 loc = Position.create(TridentServer.WORLD, 0, 0, 0); 422 velocity = new Vector(0, 0, 0); 423 424 this.uniqueId = new UUID(uuidMost.value(), uuidLeast.value()); 425 426 double[] location = new double[3]; 427 428 for (int i = 0; i < 3; i += 1) { 429 NBTTag t = pos.get(i); 430 431 if (t instanceof DoubleTag) { 432 location[i] = ((DoubleTag) t).value(); 433 } else { 434 location[i] = ((IntTag) t).value(); 435 } 436 } 437 438 // set x, y, and z cordinates from array 439 loc.setX(location[0]); 440 loc.setY(location[1]); 441 loc.setZ(location[2]); 442 443 double[] velocity = new double[3]; 444 445 for (int i = 0; i < 3; i += 1) { 446 NBTTag t = motion.get(i); 447 448 if (t instanceof DoubleTag) { 449 velocity[i] = ((DoubleTag) t).value(); 450 } else { 451 velocity[i] = ((IntTag) t).value(); 452 } 453 } 454 455 // set velocity from array 456 this.velocity.setX(velocity[0]); 457 this.velocity.setY(velocity[1]); 458 this.velocity.setZ(velocity[2]); 459 460 // set yaw and pitch from NBTTag 461 if (rotation.get(0) instanceof IntTag) { 462 loc.setYaw(((IntTag) rotation.get(0)).value()); 463 } else { 464 loc.setYaw(((FloatTag) rotation.get(0)).value()); 465 } 466 467 if (rotation.get(1) instanceof IntTag) { 468 loc.setPitch(((IntTag) rotation.get(1)).value()); 469 } else { 470 loc.setPitch(((FloatTag) rotation.get(1)).value()); 471 } 472 473 this.fallDistance.set( 474 (long) fallDistance.value()); // FIXME: may lose precision, consider changing AtomicLong 475 this.fireTicks.set(fireTicks.value()); 476 this.airTicks.set(airTicks.value()); 477 this.portalCooldown.set(portalCooldown.value()); 478 479 this.onGround = onGround.value() == 1; 480 this.godMode = invulnerable.value() == 1; 481 482 this.nameVisible = dnVisible.value() == 1; 483 this.silent = silent.value() == 1; 484 this.displayName = displayName.value(); 485 486 doLoad(tag); 487 } 488 489 public CompoundTag asNbt() { 490 CompoundTag tag = new CompoundTag("lel"); 491 // TODO 492 return tag; 493 } 494 495 @Override 496 public void setSize(float width, float height){ 497 if (width != this.width || height != this.height){ 498 this.width = width; 499 this.height = height; 500 this.boundingBox = new BoundingBox(boundingBox.minX(), boundingBox.minY(), boundingBox.minZ(), boundingBox.minX() + (double) width, boundingBox.minY() + (double) height, boundingBox.minZ() + (double) width); 501 } 502 } 503 504 @Override 505 public BoundingBox boundingBox(){ 506 return boundingBox; 507 } 508 509}