summaryrefslogblamecommitdiff
path: root/src/game-server/inventory.cpp
blob: e0b7e1c56f0c0bfec314558c4f6e9f1dc8718829 (plain) (tree)
1
2
3
4
5
6
7
8
9
10
11
  
                   
                                                            
  
                                         
  
                                                                           



                                                                        
                                                                      




                                                                     
                                                                            

   
                    

                  



                                    
                              
                           
                         
 

                                              

 
 

                                

                                                              

                                       
                                          

                                                                    
     
                                 


                                          

     



                                                                 
     


                                                                 

     
                                       

 
                            
 
      
                                                                       
       
                                   
 


                                                                     
                                
                                                                        
     

                                                                   
         


                                                                      
                                                          
                                                          
                  
         
            
         

                                                                        
                                             
                              
                                          
         
     
 

                    
      


                                                                          
       

                                                                          
     























                                                                        
         
                                                    
                                                    
         
 

              
 
                         

 
                                    
 


                                                                         
                                                                  
                                                                      
                                                                  

                                                    
                                                                      
     






                                                            
                                         
                          

                                          

                                                                 













                                                                               
     

 
                                                        
 

                                                                    
 
 
                                                                        
 
                           
                 


                                                                            



                                                                            


                                                                
     
                                        
         
                                                     

                                                

                                                      

                                                                      


                                            
                                                                           


                



                                                                       

             


                                                 
                        
                      
         
     
 


                                                      
     
                    
                  
                                                               
                                    
         



                                                         
                                                                             


                                                                      
         
                                                            

                         

     



                                                          
                         
 
                  

 
                                                        
 






                                                                 

 
                                                                                    
 
                     


                                       



                                                                 
         
                       
             


                                                                       
                                             

                                      

                                                         


                                                                            
                                                      


                    
                                         

                                               
             
                

                                                                              
                                              
         

                              
                                                                             



                                                


                                                                             

 

                                                                    
 
                                                              
                      
 


                                                               
 
                       
                      
 

                                       










                                                                                
 
                                                          
                               
         

                                                             
         
            
         
                                 
                                        
         


                                                                              


        





                                                                               
 


                                 
 
                                                          
                               
         

                                                             
         
            
         
                                 
                                        
         


                                                             
     



                                                


                  
                                                                              
 
                                                             

                                                                  
                                     
                      
 
                                       




                                                                           
     
                                                   
                                           



                          
     

                                                                    
                                                       

     


                                                           
                                 
                          
     

                                             
     

        
                             
                                   
     



                                                


                  
 
                                                                       
 
                         
               

                                                             

 
                                                                 
 


                                                                             
               
                     
                                                   
                  
                                                  
                  
                                                
 
 






                                                                     

                                                                      


                                 

                                     

                                                         
                                                   




                                                           

                                                      






                                                   
         





                                                                          
             

















                                                      
                                                                  
                                                                          




                                      

                                                                      







                                                                              

                                                                
                                             
                                                                
             

                                                  






















                                                                               
         
     


                                                  



                                    

                                                                     

                                                                     

                                                             
           
 

                                                                              
 
                                                                  
     




                                                                                

 
                                               
 
                                   
 
 

                                                                    


                                                                    
 
                                     


                                                                     
                              

                           
                                          
         





                 
                
     
                                                             
                                  
                               
     
 


                                                  
                   
 
/*
 *  The Mana Server
 *  Copyright (C) 2004-2010  The Mana World Development Team
 *
 *  This file is part of The Mana Server.
 *
 *  The Mana Server is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  any later version.
 *
 *  The Mana Server is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with The Mana Server.  If not, see <http://www.gnu.org/licenses/>.
 */

#include <algorithm>
#include <cassert>

#include "game-server/gamehandler.h"
#include "game-server/inventory.h"
#include "game-server/item.h"
#include "game-server/itemmanager.h"
#include "game-server/state.h"
#include "net/messageout.h"
#include "utils/logger.h"

Inventory::Inventory(Character *p):
    mPoss(&p->getPossessions()), mCharacter(p)
{
}

void Inventory::sendFull() const
{
    /* Sends all the information needed to construct inventory
       and equipment to the client */
    MessageOut m(GPMSG_INVENTORY_FULL);

    m.writeInt16(mPoss->inventory.size());
    for (InventoryData::const_iterator l = mPoss->inventory.begin(),
         l_end = mPoss->inventory.end(); l != l_end; ++l)
    {
        assert(l->second.itemId);
        m.writeInt16(l->first); // Slot id
        m.writeInt16(l->second.itemId);
        m.writeInt16(l->second.amount);
    }

    for (EquipData::const_iterator k = mPoss->equipSlots.begin(),
         k_end = mPoss->equipSlots.end();
         k != k_end;
         ++k)
    {
        m.writeInt16(k->first);                  // Equip slot id
        m.writeInt16(k->second.itemId);          // Item id
        m.writeInt16(k->second.itemInstance);    // Item instance
    }

    gameHandler->sendTo(mCharacter, m);
}

void Inventory::initialize()
{
    /*
     * Construct a set of item Ids to keep track of duplicate item Ids.
     */
    std::set<unsigned int> itemIds;

    /*
     * Construct a set of itemIds to keep track of duplicate itemIds.
     */
    InventoryData::iterator it1;
    for (it1 = mPoss->inventory.begin(); it1 != mPoss->inventory.end();)
    {
        ItemClass *item = itemManager->getItem(it1->second.itemId);
        if (item)
        {
            // If the insertion succeeded, it's the first time we're
            // adding the item in the inventory. Hence, we can trigger
            // item presence in inventory effect.
            if (itemIds.insert(it1->second.itemId).second)
                item->useTrigger(mCharacter, ITT_IN_INVY);
            ++it1;
        }
        else
        {
            LOG_WARN("Inventory: deleting unknown item type "
                     << it1->second.itemId << " from the inventory of '"
                     << mCharacter->getName()
                     << "'!");
            mPoss->inventory.erase(it1++);
        }
    }

    itemIds.clear();

    /*
     * Equipment effects can be cumulative if more than one item instance
     * is equipped, but we check to trigger the item presence in equipment
     * effect only based on the first item instance insertion.
     */
    EquipData::iterator it2;
    for (it2 = mPoss->equipSlots.begin(); it2 != mPoss->equipSlots.end();)
    {
        /*
         * TODO: Check that all needed slots are available here.
         */
        ItemClass *item = itemManager->getItem(it2->second.itemId);
        if (item)
        {
            // TODO: Check equip conditions.
            // If not all needed slots are there, put the item back
            // in the inventory.
        }
        else
        {
            LOG_WARN("Equipment: deleting unknown item id "
                     << it2->second.itemId << " from the equipment of '"
                     << mCharacter->getName()
                     << "'!");
            mPoss->equipSlots.erase(++it2);
            continue;
        }

        /*
         * Apply all equip triggers at first item instance insertion
         */
        if (itemIds.insert(it2->second.itemInstance).second)
        {
            itemManager->getItem(it2->second.itemId)
                ->useTrigger(mCharacter, ITT_EQUIP);
        }

        ++it2;
    }

    checkInventorySize();
}

void Inventory::checkInventorySize()
{
    /*
     * Check that the inventory size is greater than or equal to the size
     *       needed.
     *       If not, forcibly drop items from the end until it is.
     * Check that inventory capacity is greater than or equal to zero.
     *       If not, forcibly drop items from the end until it is.
     */
    while (mPoss->inventory.size() > INVENTORY_SLOTS
           || mCharacter->getModifiedAttribute(ATTR_INV_CAPACITY) < 0)
    {
        LOG_WARN("Inventory: oversize inventory! Deleting '"
                 << mPoss->inventory.rbegin()->second.amount
                 << "' items of type '"
                 << mPoss->inventory.rbegin()->second.itemId
                 << "' from slot '"
                 << mPoss->inventory.rbegin()->first
                 << "' of character '"
                 << mCharacter->getName()
                 << "'!");

        // Remove the items from inventory
        removeFromSlot(mPoss->inventory.rbegin()->first,
                       mPoss->inventory.rbegin()->second.amount);

        // Drop them on the floor
        ItemClass *ic = itemManager->getItem(mPoss->inventory.rbegin()->first);
        int nb = mPoss->inventory.rbegin()->second.amount;
        Item *item = new Item(ic, nb);
        item->setMap(mCharacter->getMap());
        item->setPosition(mCharacter->getPosition());
        if (!GameState::insert(item))
        {
            // Warn about drop failure
            LOG_WARN("Impossible to drop " << nb << " item(s) id: "
                     << ic->getDatabaseID() << " for character: '"
                     << mCharacter->getName() << "'!");
        }
    }
}

unsigned int Inventory::getItem(unsigned int slot) const
{
    InventoryData::iterator item = mPoss->inventory.find(slot);
    return item != mPoss->inventory.end() ? item->second.itemId : 0;
}

unsigned int Inventory::insert(unsigned int itemId, unsigned int amount)
{
    if (!itemId || !amount)
        return 0;

    MessageOut invMsg(GPMSG_INVENTORY);
    unsigned int maxPerSlot = itemManager->getItem(itemId)->getMaxPerSlot();

    LOG_DEBUG("Inventory: Inserting " << amount << " item(s) Id: " << itemId
              << " for character '" << mCharacter->getName() << "'.");

    InventoryData::iterator it, it_end = mPoss->inventory.end();
    // Add to slots with existing items of this type first.
    for (it = mPoss->inventory.begin(); it != it_end; ++it)
    {
        if (it->second.itemId == itemId)
        {
            // If the slot is full, try the next slot
            if (it->second.amount >= maxPerSlot)
                continue;

            // Add everything that'll fit to the stack
            unsigned short spaceLeft = maxPerSlot - it->second.amount;
            if (spaceLeft >= amount)
            {
                it->second.amount += amount;
                amount = 0;
                LOG_DEBUG("Everything inserted at slot id: " << it->first);
            }
            else
            {
                it->second.amount += spaceLeft;
                amount -= spaceLeft;
                LOG_DEBUG(spaceLeft << " item(s) inserted at slot id: "
                          << it->first);
            }

            invMsg.writeInt16(it->first);
            invMsg.writeInt16(itemId);
            invMsg.writeInt16(it->second.amount);
            if (!amount)
                break;
        }
    }

    int slot = 0;
    // We still have some left, so add to blank slots.
    for (it = mPoss->inventory.begin();; ++it)
    {
        if (!amount)
            break;
        int lim = (it == it_end) ? INVENTORY_SLOTS : it->first;
        while (amount && slot < lim)
        {
            int additions = std::min(amount, maxPerSlot);
            mPoss->inventory[slot].itemId = itemId;
            mPoss->inventory[slot].amount = additions;
            amount -= additions;
            LOG_DEBUG(additions << " item(s) inserted at slot id: " << slot);
            invMsg.writeInt16(slot++); // Last read, so also increment
            invMsg.writeInt16(itemId);
            invMsg.writeInt16(additions);
        }
        ++slot; // Skip the slot that the iterator points to
        if (it == it_end)
            break;
    }

    // Send that first, before checking potential removals
    if (invMsg.getLength() > 2)
        gameHandler->sendTo(mCharacter, invMsg);

    checkInventorySize();

    return amount;
}

unsigned int Inventory::count(unsigned int itemId) const
{
    unsigned int nb = 0;
    for (InventoryData::iterator it = mPoss->inventory.begin(),
                                 it_end = mPoss->inventory.end();
         it != it_end; ++it)
        if (it->second.itemId == itemId)
            nb += it->second.amount;
    return nb;
}

unsigned int Inventory::remove(unsigned int itemId, unsigned int amount, bool force)
{
    bool inv = false;

    MessageOut invMsg(GPMSG_INVENTORY);
    bool triggerLeaveInventory = true;
    for (InventoryData::iterator it = mPoss->inventory.begin(),
                                 it_end = mPoss->inventory.end();
         it != it_end; ++it)
        if (it->second.itemId == itemId)
        {
            if (amount)
            {
                unsigned int sub = std::min(amount, it->second.amount);
                amount -= sub;
                it->second.amount -= sub;
                invMsg.writeInt16(it->first);
                if (it->second.amount)
                {
                    invMsg.writeInt16(it->second.itemId);
                    invMsg.writeInt16(it->second.amount);
                    // Some still exist, and we have none left to remove, so
                    // no need to run leave invy triggers.
                    if (!amount)
                        triggerLeaveInventory = false;
                }
                else
                {
                    invMsg.writeInt16(0);
                    mPoss->inventory.erase(it);
                }
            }
            else
                // We found an instance of them existing and have none left to
                // remove, so no need to run leave invy triggers.
                triggerLeaveInventory = false;
        }

    if (triggerLeaveInventory)
        itemManager->getItem(itemId)->useTrigger(mCharacter, ITT_LEAVE_INVY);

    if (invMsg.getLength() > 2)
        gameHandler->sendTo(mCharacter, invMsg);

    // Rather inefficient, but still usable for now assuming small invy size.
    // FIXME
    return inv && !force ? remove(itemId, amount, true) : amount;
}

unsigned int Inventory::move(unsigned int slot1, unsigned int slot2,
                             unsigned int amount)
{
    if (!amount || slot1 == slot2 || slot2 >= INVENTORY_SLOTS)
        return amount;

    InventoryData::iterator it1 = mPoss->inventory.find(slot1),
                            it2 = mPoss->inventory.find(slot2),
                            inv_end = mPoss->inventory.end();

    if (it1 == inv_end)
        return amount;

    MessageOut invMsg(GPMSG_INVENTORY);

    unsigned int nb = std::min(amount, it1->second.amount);
    if (it2 == inv_end)
    {
        // Slot2 does not yet exist.
        mPoss->inventory[slot2].itemId = it1->second.itemId;
        nb = std::min(itemManager->getItem(it1->second.itemId)->getMaxPerSlot(),
                      nb);

        mPoss->inventory[slot2].amount = nb;
        it1->second.amount -= nb;
        amount -= nb;

        invMsg.writeInt16(slot1);                  // Slot
        if (it1->second.amount)
        {
            invMsg.writeInt16(it1->second.itemId); // Item Id
            invMsg.writeInt16(it1->second.amount); // Amount
        }
        else
        {
            invMsg.writeInt16(0);
            mPoss->inventory.erase(it1);
        }
        invMsg.writeInt16(slot2);                  // Slot
        invMsg.writeInt16(it1->second.itemId);     // Item Id (same as slot 1)
        invMsg.writeInt16(nb);                     // Amount
    }
    else
    {
        // Slot2 exists.
        if (it2->second.itemId != it1->second.itemId)
            return amount; // Cannot stack items of a different type.
        nb = std::min(itemManager->getItem(it1->second.itemId)->getMaxPerSlot()
                          - it2->second.amount,
                      nb);

        it1->second.amount -= nb;
        it2->second.amount += nb;
        amount -= nb;

        invMsg.writeInt16(slot1);                  // Slot
        if (it1->second.amount)
        {
            invMsg.writeInt16(it1->second.itemId); // Item Id
            invMsg.writeInt16(it1->second.amount); // Amount
        }
        else
        {
            invMsg.writeInt16(0);
            mPoss->inventory.erase(it1);
        }
        invMsg.writeInt16(slot2);                  // Slot
        invMsg.writeInt16(it2->second.itemId);     // Item Id
        invMsg.writeInt16(it2->second.amount);     // Amount
    }

    if (invMsg.getLength() > 2)
        gameHandler->sendTo(mCharacter, invMsg);

    return amount;
}

unsigned int Inventory::removeFromSlot(unsigned int slot, unsigned int amount)
{
    InventoryData::iterator it = mPoss->inventory.find(slot);

    // When the given slot doesn't exist, we can't remove anything
    if (it == mPoss->inventory.end())
        return amount;

    MessageOut invMsg(GPMSG_INVENTORY);
    // Check if an item of the same class exists elsewhere in the inventory
    bool exists = false;
    for (InventoryData::const_iterator it2 = mPoss->inventory.begin(),
         it2_end = mPoss->inventory.end();
         it2 != it2_end; ++it2)
    {
        if (it2->second.itemId == it->second.itemId
                && it->first != it2->first)
        {
            exists = true;
            break;
        }
    }
    if (!exists && it->second.itemId) {
        if (ItemClass *ic = itemManager->getItem(it->second.itemId))
            ic->useTrigger(mCharacter, ITT_LEAVE_INVY);
    }

    unsigned int sub = std::min(amount, it->second.amount);
    amount -= sub;
    it->second.amount -= sub;
    invMsg.writeInt16(it->first);
    if (it->second.amount)
    {
        invMsg.writeInt16(it->second.itemId);
        invMsg.writeInt16(it->second.amount);
    }
    else
    {
        invMsg.writeInt16(0);
        mPoss->inventory.erase(it);
    }

    if (invMsg.getLength() > 2)
        gameHandler->sendTo(mCharacter, invMsg);

    return amount;
}


void Inventory::changeEquipment(unsigned int oldId, unsigned int newId)
{
    if (!oldId && !newId)
        return;
    changeEquipment(oldId ? itemManager->getItem(oldId) : 0,
                    newId ? itemManager->getItem(newId) : 0);
}

void Inventory::changeEquipment(ItemClass *oldI, ItemClass *newI)
{
    // This should only be called when applying changes, either directly
    // in non-delayed mode or when the changes are committed in delayed mode.
    if (!oldI && !newI)
        return;
    if (oldI && newI)
        oldI->useTrigger(mCharacter, ITT_EQUIPCHG);
    else if (oldI)
        oldI->useTrigger(mCharacter, ITT_UNEQUIP);
    else if (newI)
        newI->useTrigger(mCharacter, ITT_EQUIP);
}

bool Inventory::equip(int slot, bool override)
{
    if (mPoss->equipSlots.count(slot))
        return false;
    InventoryData::iterator it;
    if ((it = mPoss->inventory.find(slot)) == mPoss->inventory.end())
        return false;
    const ItemEquipsInfo &eq = itemManager->getItem(it->second.itemId)
                                    ->getItemEquipData();
    if (eq.empty())
        return false;
    ItemEquipInfo const *ovd = 0;

    MessageOut equipMsg(GPMSG_EQUIP);
    // Iterate through all possible combinations of slots
    for (ItemEquipsInfo::const_iterator it2 = eq.begin(),
         it2_end = eq.end(); it2 != it2_end; ++it2)
    {
        // Iterate through this combination of slots.
        /*
         * 0 = all ok, slots free
         * 1 = possible if other items are unequipped first
         * 2 = impossible, requires too many slots
         *     even with other equipment being removed
         */
        int fail = 0;
        ItemEquipInfo::const_iterator it3, it3_end;
        for (it3 = it2->begin(),
             it3_end = it2->end();
             it3 != it3_end;
             ++it3)
        {
            // it3 -> { slot id, number required }
            unsigned int max = itemManager->getMaxSlotsFromId(it3->first),
                         used = mPoss->equipSlots.count(it3->first);
            if (max - used >= it3->second)
                continue;
            else if (max >= it3->second)
            {
                fail |= 1;
                if (override)
                    continue;
                else
                    break;
            }
            else
            {
                fail |= 2;
                break;
            }
        }
        switch (fail)
        {
            case 0:
            /*
             * Clean fit. Equip and apply immediately.
             */
            equipMsg.writeInt16(slot);           // Inventory slot
            equipMsg.writeInt16(it2->size());     // Equip slot type count
            for (it3 = it2->begin(),
                 it3_end = it2->end();
                 it3 != it3_end;
                 ++it3)
            {
                equipMsg.writeInt16(it3->first);  // Equip slot
                equipMsg.writeInt16(it3->second); // How many are used
                /*
                 * This bit can be somewhat inefficient, but is far better for
                 *  average case assuming most equip use one slot max for each
                 *  type and infrequently (<1/3) two of each type max.
                 * If the reader cares, you're more than welcome to add
                 *  compile time options optimising for other usage.
                 * For now, this is adequate assuming `normal' usage.
                 */
                /** Part disabled until reimplemented*/
                /*for (unsigned int i = 0; i < it3->second; ++i)
                    mPoss->equipSlots.insert(
                            std::make_pair(it3->first, slot));*/
            }

            changeEquipment(0, it->second.itemId);
            return true;
            case 1:
            /*
             * Definitions earlier in the item file have precedence (even if it
             *      means requiring unequipping more), so no need to store more
             *      than the first.
             */
            if (override && !ovd)
                ovd = &*it2; // Iterator -> object -> pointer.
            break;
            case 2:
            default:
            /*
             * Since slots are currently static (and I don't see any reason to
             *      change this right now), something probably went wrong.
             * The logic to catch this is here rather than in the item manager
             *      just in case non-static equip slots do want to be
             *      implemented later. This would not be a trivial task,
             *      however.
             */
            LOG_WARN("Inventory - item '" << it->second.itemId <<
                     "' cannot be equipped, even by unequipping other items!");
            break;
        }
    }

    if (equipMsg.getLength() > 2)
        gameHandler->sendTo(mCharacter, equipMsg);
    // We didn't find a clean equip.
    if (ovd)
    {
        /*
         * We did find an equip that works if we unequip other items,
         * and we can override.
         * Process unequip triggers for all items we have to unequip.
         * Process equip triggers for new item.
         * Attempt to reequip any equipment we had to remove,
         * but disallowing override.
         */

        // TODO - this would increase ease of use substatially, add as soon as
        // there is time to do so.

        return false; // Return true when this section is complete
    }
    /*
     * We cannot equip, either because we could not find any valid equip process
     *     or because we found a dirty equip and weren't allowed to override.
     */
    return false;
}

bool Inventory::unequip(EquipData::iterator it)
{
    return unequip(it->first, &it);
}

bool Inventory::unequip(unsigned int slot, EquipData::iterator *itp)
{
    EquipData::iterator it = itp ? *itp : mPoss->equipSlots.begin(),
                        it_end = mPoss->equipSlots.end();
    bool changed = false;

    MessageOut equipMsg(GPMSG_EQUIP);
    // Erase all equip entries that point to the given inventory slot
    while (it != it_end)
    {
        if (it->first == slot)
        {
            changed = true;
            mPoss->equipSlots.erase(it++);
        }
        else
        {
            ++it;
        }
    }

    if (changed)
    {
        changeEquipment(mPoss->inventory.at(slot).itemId, 0);
        equipMsg.writeInt16(slot);
        equipMsg.writeInt16(0);
    }

    if (equipMsg.getLength() > 2)
        gameHandler->sendTo(mCharacter, equipMsg);

    return changed;
}