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.Substance;
022import net.tridentsdk.event.player.PlayerClickItemEvent;
023import net.tridentsdk.inventory.Inventory;
024import net.tridentsdk.inventory.Item;
025import net.tridentsdk.registry.Registered;
026import net.tridentsdk.server.data.Slot;
027import net.tridentsdk.server.entity.TridentDroppedItem;
028import net.tridentsdk.server.event.EventProcessor;
029import net.tridentsdk.server.netty.ClientConnection;
030import net.tridentsdk.server.netty.packet.InPacket;
031import net.tridentsdk.server.netty.packet.Packet;
032import net.tridentsdk.server.player.PlayerConnection;
033import net.tridentsdk.server.player.TridentPlayer;
034import net.tridentsdk.util.TridentLogger;
035
036/**
037 * Packet sent by the player when it clicks on a slot in a inventory.
038 */
039public class PacketPlayInPlayerClickWindow extends InPacket {
040
041    /**
042     * The id of the inventory which was clicked. 0 for player inventory.
043     */
044    protected int windowId;
045    /**
046     * The button used in the click, dependent on action number  TODO reference to wiki
047     */
048    protected int clickedButton;
049
050    /**
051     * The clicked slot, -999 if not applicable
052     */
053    protected short clickedSlot;
054    /**
055     * A unique number for the action, used for transaction handling
056     */
057    protected short actionNumber;
058    /**
059     * Inventory operation mode
060     */
061    protected byte modeId;
062    protected ClickAction mode;
063    /**
064     * Item clicked
065     */
066    protected Slot clickedItem;
067
068    @Override
069    public int id() {
070        return 0x07;
071    }
072
073    public int windowId() {
074        return this.windowId;
075    }
076
077    public int clickedButton() {
078        return this.clickedButton;
079    }
080
081    public short clickedSlot() {
082        return this.clickedSlot;
083    }
084
085    public short actionNumber() {
086        return this.actionNumber;
087    }
088
089    public ClickAction mode() {
090        return this.mode;
091    }
092
093    public Slot clickedItem() {
094        return this.clickedItem;
095    }
096
097    @Override
098    public Packet decode(ByteBuf buf) {
099        this.windowId = (int) buf.readByte();
100        this.clickedSlot = buf.readShort();
101        this.clickedButton = (int) buf.readByte();
102        this.actionNumber = buf.readShort();
103        this.modeId = buf.readByte();
104        this.mode = ClickAction.getAction(modeId, clickedButton, clickedSlot);
105        this.clickedItem = new Slot(buf);
106        return this;
107    }
108
109    @Override
110    public void handleReceived(ClientConnection connection) {
111        if(mode == null) {
112            return;
113        }
114
115        TridentPlayer player = ((PlayerConnection) connection).player();
116        Inventory window = Registered.inventories().fromId(this.windowId);
117        Inventory originalWindow = window;
118
119        int originalSlot = clickedSlot;
120        if(clickedSlot >= window.length()) {
121            clickedSlot += (9 - window.length());
122            window = player.window();
123        }
124
125        PlayerClickItemEvent clickEvent = EventProcessor
126                .fire(new PlayerClickItemEvent(window, this.clickedSlot, (int) this.actionNumber));
127
128        if(clickEvent.isIgnored()) {
129            return;
130        }
131
132        // TODO Implement all
133        switch(mode) {
134            case LEFT_CLICK:
135            case RIGHT_CLICK:
136                if(player.pickedItem() == null) {
137                    if(window.itemAt(clickedSlot) != null && window.itemAt(clickedSlot).type() != Substance.AIR) {
138                        if(window.itemAt(clickedSlot).isSimilar(clickedItem.item())) {
139                            if(mode == ClickAction.LEFT_CLICK) {
140                                player.setPickedItem(clickedItem.item());
141                                window.setSlot(clickedSlot, null);
142                            } else {
143                                Item cursor = clickedItem.item().clone();
144                                cursor.setQuantity((short) Math.ceil((cursor.quantity() / 2)));
145                                player.setPickedItem(cursor);
146                                window.itemAt(clickedSlot).setQuantity((short) (window.itemAt(clickedSlot).quantity() - cursor.quantity()));
147                                window.setSlot(clickedSlot, window.itemAt(clickedSlot));
148                            }
149                        } else {
150                            TridentLogger.get().warn(player.name() + " tried to cheat items!");
151                        }
152                    }
153                } else {
154                    Item temp = window.itemAt(clickedSlot);
155                    if(mode == ClickAction.LEFT_CLICK) {
156                        window.setSlot(clickedSlot, player.pickedItem());
157                        if(temp != null && temp.type() != Substance.AIR) {
158                            player.setPickedItem(temp);
159                        } else {
160                            player.setPickedItem(null);
161                        }
162                    } else {
163                        if(temp == null || temp.type() == Substance.AIR) {
164                            Item single = player.pickedItem().clone();
165                            single.setQuantity((short) 1);
166                            window.setSlot(clickedSlot, single);
167                            if(player.pickedItem().quantity() > 1){
168                                player.pickedItem().setQuantity((short) (player.pickedItem().quantity() - 1));
169                            }else{
170                                player.setPickedItem(null);
171                            }
172                        }else{
173                            window.setSlot(clickedSlot, player.pickedItem());
174                            if(temp.type() != Substance.AIR) {
175                                player.setPickedItem(temp);
176                            } else {
177                                player.setPickedItem(null);
178                            }
179                        }
180                    }
181                }
182                break;
183            case SHIFT_LEFT_CLICK:
184            case SHIFT_RIGHT_CLICK:
185                if(window.itemAt(clickedSlot) != null && window.itemAt(clickedSlot).type() != Substance.AIR){
186                    if(originalWindow.equals(window)){
187                        if(player.window().putItem(window.itemAt(clickedSlot))){
188                            window.setSlot(clickedSlot, null);
189                        }
190                    }else{
191                        if(originalWindow.putItem(window.itemAt(clickedSlot))){
192                            window.setSlot(clickedSlot, null);
193                        }
194                    }
195                }
196                break;
197            case NUMBER_KEY:
198                break;
199            case MIDDLE_CLICK:
200                break;
201            case DROP_KEY_ONE:
202            case DROP_KEY_STACK:
203                if(window.itemAt(clickedSlot) != null && window.itemAt(clickedSlot).type() != Substance.AIR){
204                    short amount = (mode == ClickAction.DROP_KEY_STACK) ? window.itemAt(clickedSlot).quantity() : 1;
205                    Item item = window.itemAt(clickedSlot).clone();
206                    item.setQuantity(amount);
207                    TridentDroppedItem droppedItem = new TridentDroppedItem(player.position(), item);
208                    droppedItem.spawn();
209                    droppedItem.setVelocity(player.position().toDirection().normalize().multiply(2000));
210                    window.itemAt(clickedSlot).setQuantity((short) (window.itemAt(clickedSlot).quantity() - amount));
211                    if(window.itemAt(clickedSlot).quantity() == 0){
212                        window.setSlot(clickedSlot, null);
213                    }
214                }
215                break;
216            case LEFT_CLICK_OUTSIDE:
217                break;
218            case RIGHT_CLICK_OUTSIDE:
219                break;
220            case START_LEFT_CLICK_DRAG:
221            case START_RIGHT_CLICK_DRAG:
222                if(player.drag() != null){
223                    TridentLogger.get().warn(player.name() + " tried to drag whilst already dragging!");
224                    break;
225                }
226
227                player.setDrag(mode);
228                break;
229            case ADD_SLOT_LEFT_CLICK_DRAG:
230            case ADD_SLOT_RIGHT_CLICK_DRAG:
231                if(player.drag() == null){
232                    TridentLogger.get().warn(player.name() + " tried to add drag slot, whilst not dragging!");
233                    break;
234                }else{
235                    if((mode == ClickAction.ADD_SLOT_LEFT_CLICK_DRAG && player.drag() == ClickAction.START_RIGHT_CLICK_DRAG) ||
236                       (mode == ClickAction.ADD_SLOT_RIGHT_CLICK_DRAG && player.drag() == ClickAction.START_LEFT_CLICK_DRAG)){
237                        TridentLogger.get().warn(player.name() + " tried to add drag slot, whilst dragging the wrong click!");
238                        break;
239                    }
240                }
241
242                player.dragSlots().add(originalSlot);
243                break;
244            case END_LEFT_CLICK_DRAG:
245                if(player.drag() == null){
246                    TridentLogger.get().warn(player.name() + " tried to stop dragging, whilst not dragging!");
247                    break;
248                }else if(player.drag() == ClickAction.START_RIGHT_CLICK_DRAG){
249                    TridentLogger.get().warn(player.name() + " tried to stop dragging, whilst dragging the wrong click!");
250                    break;
251                }
252
253                int available = player.pickedItem().quantity();
254                int split = (int) Math.floor(available / player.dragSlots().size());
255                for (Integer i : player.dragSlots()){
256                    if(available == 0){
257                        break;
258                    }
259
260                    Inventory using = originalWindow;
261                    if(i >= originalWindow.length()){
262                        using = player.window();
263                    }
264
265                    Item current = using.itemAt(i);
266                    if(current == null || current.type() == Substance.AIR){
267                        current = player.pickedItem().clone();
268                        current.setQuantity((short) split);
269                        using.setSlot(i, current);
270                        available -= split;
271                    }else if(current.isSimilarIgnoreQuantity(player.pickedItem()) && current.quantity() < current.type().maxStackSize()){
272                        int canAdd = Math.min(split, current.type().maxStackSize() - current.quantity());
273                        current.setQuantity((short) (current.quantity() + canAdd));
274                        using.setSlot(i, current);
275                        available -= canAdd;
276                    }
277                }
278
279                if(available == 0){
280                    player.setPickedItem(null);
281                }else{
282                    player.pickedItem().setQuantity((short) available);
283                }
284
285                player.dragSlots().clear();
286                player.setDrag(null);
287                break;
288            case END_RIGHT_CLICK_DRAG:
289                if(player.drag() == null){
290                    TridentLogger.get().warn(player.name() + " tried to stop dragging, whilst not dragging!");
291                    break;
292                }else if(player.drag() == ClickAction.START_LEFT_CLICK_DRAG){
293                    TridentLogger.get().warn(player.name() + " tried to stop dragging, whilst dragging the wrong click!");
294                    break;
295                }
296
297                available = player.pickedItem().quantity();
298                for (Integer i : player.dragSlots()){
299                    if(available == 0){
300                        break;
301                    }
302
303                    Inventory using = originalWindow;
304                    if(i >= originalWindow.length()){
305                        using = player.window();
306                    }
307
308                    Item current = using.itemAt(i);
309                    if(current == null || current.type() == Substance.AIR){
310                        current = player.pickedItem().clone();
311                        current.setQuantity((short) 1);
312                        using.setSlot(i, current);
313                        available--;
314                    }else if(current.isSimilarIgnoreQuantity(player.pickedItem()) && current.quantity() < current.type().maxStackSize()){
315                        current.setQuantity((short) (current.quantity() + 1));
316                        using.setSlot(i, current);
317                        available--;
318                    }
319                }
320
321                if(available == 0){
322                    player.setPickedItem(null);
323                }else{
324                    player.pickedItem().setQuantity((short) available);
325                }
326
327                player.dragSlots().clear();
328                player.setDrag(null);
329                break;
330            case DOUBLE_CLICK:
331                Item picking = window.itemAt(clickedSlot);
332                if(player.pickedItem() != null) {
333                    picking = player.pickedItem();
334                }
335
336                int count = picking.quantity();
337                int slot = 0;
338                if(window.id() != windowId) {
339                    window = originalWindow;
340                }
341
342                while (count <= picking.type().maxStackSize() && slot < window.length()) {
343                    if(window.itemAt(slot) != null && window.itemAt(slot).isSimilarIgnoreQuantity(picking)) {
344                        if(count + window.itemAt(slot).quantity() <= picking.type().maxStackSize()) {
345                            count += window.itemAt(slot).quantity();
346                            window.setSlot(slot, null);
347                        } else {
348                            window.itemAt(slot).setQuantity((short) (window.itemAt(slot).quantity() - (picking.type().maxStackSize() - count)));
349                            window.setSlot(slot, window.itemAt(slot));
350                            count = picking.type().maxStackSize();
351                            break;
352                        }
353                    }
354                    slot++;
355                }
356
357                if(count < picking.type().maxStackSize() && windowId > 0) {
358                    slot = 0;
359                    Inventory pW = player.window();
360                    while (count <= picking.type().maxStackSize() && slot < pW.length()) {
361                        if(pW.itemAt(slot) != null && pW.itemAt(slot).isSimilarIgnoreQuantity(picking)) {
362                            if(count + pW.itemAt(slot).quantity() <= picking.type().maxStackSize()) {
363                                count += pW.itemAt(slot).quantity();
364                                pW.setSlot(slot, null);
365                            } else {
366                                pW.itemAt(slot).setQuantity((short) (pW.itemAt(slot).quantity() - (picking.type().maxStackSize() - count)));
367                                pW.setSlot(slot, pW.itemAt(slot));
368                                count = picking.type().maxStackSize();
369                                break;
370                            }
371                        }
372                        slot++;
373                    }
374                }
375
376                picking.setQuantity((short) count);
377                player.setPickedItem(picking);
378                break;
379        }
380    }
381
382    public enum ClickAction {
383
384        LEFT_CLICK,
385        RIGHT_CLICK,
386
387        SHIFT_LEFT_CLICK,
388        SHIFT_RIGHT_CLICK,
389
390        NUMBER_KEY,
391
392        MIDDLE_CLICK,
393
394        DROP_KEY_ONE,
395        DROP_KEY_STACK,
396        LEFT_CLICK_OUTSIDE,
397        RIGHT_CLICK_OUTSIDE,
398
399        START_LEFT_CLICK_DRAG,
400        START_RIGHT_CLICK_DRAG,
401        ADD_SLOT_LEFT_CLICK_DRAG,
402        ADD_SLOT_RIGHT_CLICK_DRAG,
403        END_LEFT_CLICK_DRAG,
404        END_RIGHT_CLICK_DRAG,
405
406        DOUBLE_CLICK;
407
408        public static ClickAction getAction(short mode, int button, int slot) {
409            switch(mode) {
410                case 0:
411                case 255:
412                    switch(button) {
413                        case 0:
414                            return LEFT_CLICK;
415                        case 1:
416                            return RIGHT_CLICK;
417                    }
418                    break;
419                case 1:
420                    switch(button) {
421                        case 0:
422                            return SHIFT_LEFT_CLICK;
423                        case 1:
424                            return SHIFT_RIGHT_CLICK;
425                    }
426                    break;
427                case 2:
428                    return NUMBER_KEY;
429                case 3:
430                    return MIDDLE_CLICK;
431                case 4:
432                    if(slot == -999) {
433                        switch(button) {
434                            case 0:
435                                return LEFT_CLICK_OUTSIDE;
436                            case 1:
437                                return RIGHT_CLICK_OUTSIDE;
438                        }
439                    } else {
440                        switch(button) {
441                            case 0:
442                                return DROP_KEY_ONE;
443                            case 1:
444                                return DROP_KEY_STACK;
445                        }
446                    }
447                    break;
448                case 5:
449                case 1535:
450                    switch(button) {
451                        case 0:
452                            return START_LEFT_CLICK_DRAG;
453                        case 4:
454                            return START_RIGHT_CLICK_DRAG;
455                        case 1:
456                            return ADD_SLOT_LEFT_CLICK_DRAG;
457                        case 5:
458                            return ADD_SLOT_RIGHT_CLICK_DRAG;
459                        case 2:
460                            return END_LEFT_CLICK_DRAG;
461                        case 6:
462                            return END_RIGHT_CLICK_DRAG;
463                    }
464                    break;
465                case 6:
466                case 1791:
467                    return DOUBLE_CLICK;
468            }
469
470            return null;
471        }
472
473    }
474
475}