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.Position;
022import net.tridentsdk.effect.entity.EntityStatusEffect;
023import net.tridentsdk.effect.entity.EntityStatusEffectType;
024import net.tridentsdk.entity.Entity;
025import net.tridentsdk.entity.LivingEntity;
026import net.tridentsdk.entity.Projectile;
027import net.tridentsdk.entity.living.Player;
028import net.tridentsdk.entity.living.ai.AiHandler;
029import net.tridentsdk.entity.living.ai.AiModule;
030import net.tridentsdk.entity.living.ai.Path;
031import net.tridentsdk.entity.traits.EntityProperties;
032import net.tridentsdk.entity.types.EntityType;
033import net.tridentsdk.meta.nbt.*;
034import net.tridentsdk.server.data.MetadataType;
035import net.tridentsdk.server.data.ProtocolMetadata;
036import net.tridentsdk.server.effect.entity.TridentEntityStatusEffect;
037import net.tridentsdk.server.entity.ai.TridentAiHandler;
038import net.tridentsdk.server.packets.play.out.PacketPlayOutDestroyEntities;
039import net.tridentsdk.server.packets.play.out.PacketPlayOutSpawnMob;
040import net.tridentsdk.server.player.TridentPlayer;
041import net.tridentsdk.util.Vector;
042
043import java.util.List;
044import java.util.UUID;
045import java.util.concurrent.CopyOnWriteArrayList;
046import java.util.concurrent.atomic.AtomicInteger;
047
048/**
049 * An entity that has health
050 *
051 * @author The TridentSDK Team
052 */
053public abstract class TridentLivingEntity extends TridentEntity implements LivingEntity {
054    private volatile AiModule ai;
055    private volatile Path path;
056
057    protected final List<EntityAttribute> attributes = new CopyOnWriteArrayList<>();
058    protected final AtomicInteger invincibilityTicks = new AtomicInteger(0);
059    protected final AtomicInteger restTicks = new AtomicInteger(0);
060    /**
061     * The entity health
062     */
063    protected final AtomicDouble health = new AtomicDouble(0.0);
064    /**
065     * Whether the entity is dead
066     */
067    protected volatile boolean dead;
068    /**
069     * Whether the entity can pick up items
070     */
071    protected volatile boolean canPickup = true;
072    /**
073     * The maximum available health
074     */
075    protected volatile double maxHealth;
076
077    /**
078     * Inherits from {@link TridentEntity}
079     *
080     * <p>The entity is immediately set "non-dead" after {@code super} call</p>
081     */
082    public TridentLivingEntity(UUID id, Position spawnLocation) {
083        super(id, spawnLocation);
084
085        this.dead = false;
086    }
087
088    @Override
089    protected void encodeMetadata(ProtocolMetadata protocolMeta) {
090        super.encodeMetadata(protocolMeta);
091
092        protocolMeta.setMeta(2, MetadataType.STRING, displayName);
093        protocolMeta.setMeta(3, MetadataType.BYTE, nameVisible ? (byte) 1 : (byte) 0);
094        protocolMeta.setMeta(6, MetadataType.FLOAT, health.floatValue());
095        protocolMeta.setMeta(7, MetadataType.INT, 0);
096        protocolMeta.setMeta(8, MetadataType.BYTE, (byte) 1); // TODO (potion effects)
097        protocolMeta.setMeta(9, MetadataType.BYTE, (byte) 0); // TODO (arrows in entity)
098        protocolMeta.setMeta(15, MetadataType.BYTE, (ai == null) ? (byte) 1 : (byte) 0);
099    }
100
101    @Override
102    protected void doTick() {
103        performAiUpdate();
104    }
105
106    @Override
107    public double health() {
108        return this.health.get();
109    }
110
111    @Override
112    public void setHealth(double health) {
113        this.health.set(health);
114    }
115
116    @Override
117    public double maxHealth() {
118        return this.maxHealth;
119    }
120
121    @Override
122    public void setMaxHealth(double maxHealth) {
123        this.maxHealth = maxHealth;
124    }
125
126    @Override
127    public Position headLocation() {
128        return this.position().relative(new Vector(0.0d, 1.0d, 0.0d));
129    }
130
131    @Override
132    public long remainingAir() {
133        return this.airTicks.get();
134    }
135
136    @Override
137    public void setRemainingAir(long ticks) {
138        this.airTicks.set((int) ticks);
139    }
140
141    @Override
142    public boolean canCollectItems() {
143        return this.canPickup;
144    }
145
146    @Override
147    public boolean isDead() {
148        return this.dead;
149    }
150
151    @Override
152    public void remove() {
153        super.remove();
154        dead = true;
155    }
156
157    @Override
158    public void setAiModule(AiModule module) {
159        this.ai = module;
160    }
161
162    @Override
163    public AiModule aiModule() {
164        AiModule module = this.ai;
165        if (module == null) {
166            return aiHandler().defaultAiFor(type());
167        } else {
168            return module;
169        }
170    }
171
172    private static final AiHandler AI_HANDLER = new TridentAiHandler();
173
174    public static AiHandler aiHandler() {
175        return AI_HANDLER;
176    }
177
178    @Override
179    public <T extends Projectile> T launchProjectile(EntityProperties properties) {
180        return null;
181    }
182
183    public void performAiUpdate() {
184        AiModule module = this.aiModule();
185
186        if (this.restTicks.get() <= 0) {
187            this.restTicks.set(module.think(this));
188        } else {
189            this.restTicks.getAndDecrement();
190            // TODO: follow path
191        }
192    }
193
194    @Override
195    public Path path() {
196        return path;
197    }
198
199    public void setPath(Path path) {
200        this.path = path;
201    }
202
203    @Override
204    public void hide(Entity entity) {
205        PacketPlayOutDestroyEntities packet = new PacketPlayOutDestroyEntities();
206        packet.set("destroyedEntities", new int[]{ entity.entityId() });
207
208        if (this instanceof Player) {
209            ((TridentPlayer) this).connection().sendPacket(packet);
210        }
211    }
212
213    @Override
214    public void show(Entity entity) {
215        PacketPlayOutSpawnMob packet = new PacketPlayOutSpawnMob();
216        ProtocolMetadata protocolMeta = new ProtocolMetadata();
217
218        ((TridentEntity) entity).encodeMetadata(protocolMeta);
219
220        packet.set("entityId", entity.entityId())
221                .set("entity", entity)
222                .set("metadata", protocolMeta);
223
224        if (this instanceof Player) {
225            ((TridentPlayer) this).connection().sendPacket(packet);
226        }
227    }
228
229    @Override
230    public void load(CompoundTag tag) {
231        super.load(tag);
232
233        if (type() == EntityType.PLAYER) {
234            return; // onlinePlayers do not inherit the living entity or "mob" NBT structure
235        }
236
237        if (tag.containsTag("HealF")) {
238            health.set(((FloatTag) tag.getTag("HealF")).value());
239        } else {
240            health.set(((ShortTag) tag.getTag("Health")).value());
241        }
242
243        FloatTag extraHealth = tag.getTagAs("AbsorptionAmount"); // health added if the entity has the absorption effect
244
245        ShortTag invincibilityTicks = tag.getTagAs("AttackTime"); // time in ticks that the entity is invincible
246        ShortTag hurtTime = tag.getTagAs("HurtTime"); // time in ticks that the entity is shown as "red" for being hit
247        ShortTag timeDead = tag.getTagAs("DeathTime"); // time in ticks entity has been dead for
248
249        ListTag attributes = tag.getTagAs("Attributes");
250        ListTag potionEffects = tag.getTagAs("ActiveEffects");
251
252        ByteTag canPickupLoot = tag.getTagAs("CanPickupLoot");
253        ByteTag aiDisabled = tag.getTagAs("NoAI");
254        ByteTag canRespawn = tag.getTagAs("PersistenceRequired");
255        ByteTag leashed = tag.getTagAs("Leashed");
256
257        health.addAndGet(extraHealth.value());
258        this.invincibilityTicks.set(invincibilityTicks.value());
259
260        for (NBTTag attribute : attributes.listTags()) {
261            this.attributes.add(NBTSerializer.deserialize(EntityAttribute.class,
262                    attribute.asType(CompoundTag.class)));
263        }
264    }
265
266    @Override
267    public EntityStatusEffect createStatusEffect(EntityStatusEffectType status){
268        return new TridentEntityStatusEffect(this, status);
269    }
270}