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}