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.packets.play.in;
019
020import io.netty.buffer.ByteBuf;
021import net.tridentsdk.base.Block;
022import net.tridentsdk.base.BlockDirection;
023import net.tridentsdk.base.Position;
024import net.tridentsdk.base.Substance;
025import net.tridentsdk.effect.particle.ParticleEffect;
026import net.tridentsdk.effect.particle.ParticleEffectType;
027import net.tridentsdk.effect.sound.SoundEffect;
028import net.tridentsdk.effect.sound.SoundEffectType;
029import net.tridentsdk.entity.types.EntityType;
030import net.tridentsdk.event.Cancellable;
031import net.tridentsdk.event.Event;
032import net.tridentsdk.event.block.BlockBreakEvent;
033import net.tridentsdk.event.player.*;
034import net.tridentsdk.inventory.Item;
035import net.tridentsdk.meta.block.Tile;
036import net.tridentsdk.registry.Registered;
037import net.tridentsdk.server.entity.TridentDroppedItem;
038import net.tridentsdk.server.entity.projectile.TridentArrow;
039import net.tridentsdk.server.netty.ClientConnection;
040import net.tridentsdk.server.netty.packet.InPacket;
041import net.tridentsdk.server.netty.packet.Packet;
042import net.tridentsdk.server.packets.play.out.PacketPlayOutBlockChange;
043import net.tridentsdk.server.player.PlayerConnection;
044import net.tridentsdk.server.player.TridentPlayer;
045import net.tridentsdk.server.world.TridentChunk;
046import net.tridentsdk.server.world.TridentWorld;
047import net.tridentsdk.util.TridentLogger;
048import net.tridentsdk.util.Vector;
049import net.tridentsdk.world.settings.GameMode;
050
051public class PacketPlayInPlayerDig extends InPacket {
052    private short status;
053    private Position location;
054    private short blockFace;
055
056    @Override
057    public int id() {
058        return 0x13;
059    }
060
061    public short status() {
062        return this.status;
063    }
064
065    public Position location() {
066        return this.location;
067    }
068
069    public short blockFace() {
070        return this.blockFace;
071    }
072
073    @Override
074    public Packet decode(ByteBuf buf) {
075        this.status = (short) buf.readByte();
076        long encodedLocation = buf.readLong();
077
078        this.location = Position.create(null, (double) (encodedLocation >> 38),
079                (double) (encodedLocation << 26 >> 52), (double) (encodedLocation << 38 >> 38));
080        this.blockFace = (short) buf.readByte();
081
082        return this;
083    }
084
085    @Override
086    public void handleReceived(ClientConnection connection) {
087        TridentPlayer player = ((PlayerConnection) connection).player();
088        DigStatus digStatus = DigStatus.getStatus(this.status);
089        BlockDirection face = null;
090
091        if (digStatus == DigStatus.DIG_START && player.gameMode() == GameMode.CREATIVE) {
092            digStatus = DigStatus.DIG_FINISH;
093        }
094
095        this.location.setWorld(player.world());
096
097        switch (this.blockFace) {
098            case 0:
099                face = BlockDirection.BOTTOM;
100                break;
101
102            case 1:
103                face = BlockDirection.TOP;
104                break;
105
106            case 2:
107                // z--
108                break;
109
110            case 3:
111                // z++
112                break;
113
114            case 4:
115                // x--
116                break;
117
118            case 5:
119                // x++
120                break;
121
122            default:
123                TridentLogger.get().error(new IllegalArgumentException("Client sent invalid BlockFace!"));
124        }
125
126        Cancellable event = null;
127
128        Block block = location.block();
129        switch (digStatus) {
130            case DIG_START:
131            case DIG_CANCEL:
132            case DIG_FINISH:
133                event = new PlayerDigEvent(player, face, this.status);
134
135                if(digStatus == DigStatus.DIG_FINISH) {
136                    BlockBreakEvent blockBreak = new BlockBreakEvent(player, block, face, player.heldItem());
137
138                    if(blockBreak.isIgnored())
139                        return;
140                }
141
142                break;
143
144            case DROP_ITEMSTACK:
145                if(player.heldItem() == null || player.heldItem().type() == Substance.AIR){
146                    return;
147                }
148
149                event = new PlayerDropItemEvent(player, null); // todo: spawn item and call the event
150                break;
151
152            case DROP_ITEM:
153                if(player.heldItem() == null || player.heldItem().type() == Substance.AIR){
154                    return;
155                }
156
157                event = new PlayerDropItemEvent(player, null);
158                break;
159
160            case SHOOT_ARROW:
161                Item item = player.heldItem();
162                if (item.type().isWeapon()) {
163                    if (item.type() == Substance.BOW) // bow
164                        event = new PlayerShootBowEvent(player, null);
165                    else event = new PlayerInteractEvent(player, block); // other weapons
166                } else if (item.type().isEdible()) {
167                    event = new PlayerConsumeEvent(player, item, 0.0);
168                }
169                // shoot bow, if player has a food item finish eating
170                break;
171            default:
172        }
173
174        Registered.events().fire((Event) event);
175
176        if (event == null || event.isIgnored())
177            return;
178
179        // TODO act accordingly
180
181        switch(digStatus){
182            case SHOOT_ARROW:
183                TridentArrow entity = (TridentArrow) location.world().spawn(EntityType.ARROW, location);
184                entity.setVelocity(player.position().asUnitVector());
185                break;
186
187            case DIG_FINISH:
188                block.ownedMeta().iterate(e -> {
189                    if (e.getValue() instanceof Tile) {
190                        ((TridentWorld) location.world()).tilesInternal().remove(e.getValue());
191                        Position pos = block.position();
192                        ((TridentChunk) pos.chunk()).tilesInternal().remove(new Vector(pos.x(), pos.y(), pos.z()));
193                    }
194                });
195
196                int[] arr = {block.substance().id() + (block.meta() << 12)};
197
198                ((TridentChunk) location().chunk()).setAt(location, Substance.AIR, (byte) 0, (byte) 255, (byte) 15);
199                TridentPlayer.sendAll(new PacketPlayOutBlockChange()
200                        .set("location", location).set("blockId", Substance.AIR.id()));
201
202                ParticleEffect effect = location.world().spawnParticle(ParticleEffectType.BLOCK_CRACK);
203                effect.setCount(64);
204                effect.setLongDistance(false);
205                effect.setPosition(location.add(new Vector(0.5, 0.5, 0.5)).asVector());
206                effect.setOffset(new Vector(0.45, 0.45, 0.45));
207                effect.setData(arr);
208                effect.applyToEveryoneExcept(player);
209
210                SoundEffectType soundEffectType = block.substance().breakSound();
211                if(soundEffectType != null){
212                    SoundEffect sound = location.world().playSound(soundEffectType);
213                    sound.setPosition(location);
214                    sound.applyToEveryoneExcept(player);
215                }
216                break;
217
218            case DROP_ITEM:
219            case DROP_ITEMSTACK:
220                short count = digStatus == DigStatus.DROP_ITEM ? 1 : player.heldItem().quantity();
221                Item heldItem = player.heldItem().clone();
222                heldItem.setQuantity(count);
223                TridentDroppedItem item = new TridentDroppedItem(player.headLocation(), heldItem);
224                item.spawn();
225                item.setVelocity(player.position().toDirection().normalize().multiply(2000));
226                player.heldItem().setQuantity((short) (player.heldItem().quantity() - count));
227                player.setHeldItem(player.heldItem());
228                break;
229        }
230
231    }
232
233    public enum DigStatus {
234        DIG_START(0),
235        DIG_CANCEL(1),
236        DIG_FINISH(2),
237        DROP_ITEMSTACK(3),
238        DROP_ITEM(4),
239        SHOOT_ARROW(5);
240
241        private final short id;
242
243        DigStatus(int id) {
244            this.id = (short) id;
245        }
246
247        public static DigStatus getStatus(short id) {
248            for (DigStatus status : DigStatus.values()) {
249                if (status.id == id) {
250                    return status;
251                }
252            }
253
254            return null;
255        }
256
257        public short getId() {
258            return this.id;
259        }
260    }
261}