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}