/*
* pscharacter.cpp
*
* Copyright (C) 2001 Atomic Blue (info@planeshift.it, http://www.atomicblue.org)
*
*
* This program 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 (version 2 of the License)
* This program 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 this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
*
*/

#include <psconfig.h>

#include <iutil/virtclk.h>
#include <csutil/databuf.h>
#include <ctype.h>

// Define this so the slot ID to string mapping is compiled into object form exactly once
#define PSCHARACTER_CPP

#include "../psserver.h"
#include "util/psdatabase.h"
#include "pscharacter.h"
#include "pscharacterloader.h"
#include "../psserverchar.h"
#include "../globals.h"
#include "util/log.h"
#include "../exchangemanager.h"
#include "../spellmanager.h"
#include "../workmanager.h"
#include "../marriagemanager.h"
#include "../cachemanager.h"
#include "../npcmanager.h"
#include "psglyph.h"
#include "psquest.h"
#include "dictionary.h"
#include "psraceinfo.h"
#include "psguildinfo.h"
#include "psmerchantinfo.h"
#include "pstrainerinfo.h"
#include "util/psxmlparser.h"
#include "util/serverconsole.h"
#include "util/mathscript.h"
#include "util/log.h"
#include "../playergroup.h"
#include "../events.h"
#include "servervitals.h"
#include "../progressionmanager.h"
#include "../chatmanager.h"
#include "../commandmanager.h"


// The sizes and scripts need balancing.  For now, maxSize is disabled.
#define ENABLE_MAX_CAPACITY 0


const char *psCharacter::characterTypeName[] = { "player", "npc", "pet" };

psCharacterInventory::psCharacterInventory()
{
    int i;
    for (i=0; i<PSCHARACTER_SLOT_COUNT; i++)
    {
        equipment[i].default_if_empty = NULL;
        equipment[i].item = NULL;
        equipment[i].NextSwingTime = 0;
        equipment[i].EquipmentFlags = 0x00000000;
    }
    for (i=0; i<PSCHARACTER_BULK_COUNT; i++)
    {
        bulk[i] = NULL;
    }

    totalWeight = 0.0f;
    totalSize = 0.0f;
    totalCount = 0;

    maxWeight = 0.0f;
    maxSize = 0.0f;

    // Load fists
    psItemStats *fist = CacheManager::GetSingleton().GetBasicItemStatsByName("Fist");
    equipment[PSCHARACTER_SLOT_LEFTHAND].default_if_empty = fist->InstantiateBasicItem();
    equipment[PSCHARACTER_SLOT_LEFTHAND].EquipmentFlags |= PSCHARACTER_EQUIPMENTFLAG_AUTOATTACK | PSCHARACTER_EQUIPMENTFLAG_ATTACKIFEMPTY;
    equipment[PSCHARACTER_SLOT_RIGHTHAND].default_if_empty = fist->InstantiateBasicItem();
    equipment[PSCHARACTER_SLOT_RIGHTHAND].EquipmentFlags |= PSCHARACTER_EQUIPMENTFLAG_AUTOATTACK | PSCHARACTER_EQUIPMENTFLAG_ATTACKIFEMPTY;

    // base clothes and natural armor are loaded when setting race.

    owner = NULL;
    doRestrictions = false;
    
    loaded = false;
}

void psCharacterInventory::Clear()
{
    unsigned int slot;
    // Delete all instances of default_if_empty items
    for (slot=0;slot<PSCHARACTER_SLOT_COUNT;slot++)
    {
        if ( equipment[slot].default_if_empty!=NULL)
        {
            delete equipment[slot].default_if_empty;
            equipment[slot].default_if_empty=NULL;
        }
        if ( equipment[slot].item != NULL)
        {
            delete equipment[slot].item;
            equipment[slot].item=NULL;
        }
    }

    // Delete instances of contained items
    for (slot=0;slot<PSCHARACTER_BULK_COUNT;slot++)
    {
        if (bulk[slot]!=NULL)
        {
            delete bulk[slot];
            bulk[slot]=NULL;
        }
    }
}


void psCharacterInventory::CalculateStats()
{
    // The max total weight that a player can carry
    static MathScript *maxCarryWeight = NULL;
    if ( !maxCarryWeight )
        maxCarryWeight = psserver->GetMathScriptEngine()->FindScript("CalculateMaxCarryWeight");
    if ( maxCarryWeight )
    {
        MathScriptVar* actorvar = maxCarryWeight->GetOrCreateVar("Actor");
        actorvar->SetObject(owner);
        
        maxCarryWeight->Execute();
        MathScriptVar* carry = maxCarryWeight->GetVar("MaxCarry");
        maxWeight = carry->GetValue();    
    }        
    
    // The max total size that a player can carry
    static MathScript *maxCarryAmount = NULL;
    if ( !maxCarryAmount )
        maxCarryAmount = psserver->GetMathScriptEngine()->FindScript("CalculateMaxCarryAmount");
    if ( maxCarryAmount )
    {
        MathScriptVar* actorvar = maxCarryAmount->GetOrCreateVar("Actor");
        actorvar->SetObject(owner);
        
        maxCarryAmount->Execute();
        MathScriptVar* carryAmount = maxCarryAmount->GetVar("MaxAmount");
        maxSize = carryAmount->GetValue();
    }                       

    ReassessInventoryDimensions();

    bool needToUpdateInv = false;
    bool droppedSomething = false;
    int cnum = 0;
    if (owner->GetActor())
        cnum = owner->GetActor()->GetClientID();

    // Drop items if can't hold everything anymore
    for (int i=PSCHARACTER_BULK_COUNT-1; i>=0; i--)
    {
        psItem* item = GetBulkItem(i);
        if (!item)
            continue;

        bool canHoldWeight = HasWeight(0);
        bool canHoldSize = HasSpace(0);
        if ( canHoldWeight && canHoldSize )
            break;  // Can hold everything
        
        // Make sure we don't drop weightless or sizeless items
        // to fix overweight or oversize, respectively
        if (canHoldWeight)
        {
            // Can hold weight, but not size; only drop due to size
            if (item->GetSumSize() < EPSILON)
                continue;  // Won't help
        }
        else // canHoldSize
        {
            // Can hold size, but not weight; only drop due to weight
            if (item->GetSumWeight() < EPSILON)
                continue;  // Won't help
        }

        psItem* removed = RemoveBulk(i);
        CS_ASSERT(removed);

        owner->DropItem(removed,removed->IsTransient());
        needToUpdateInv = true;
        droppedSomething = true;
    }

    // Check requirements of the items equiped
    for (int i=0; i<INVENTORY_EQUIP_COUNT; i++)
    {
        csString msg;
        psItem* item = equipment[i].item;
        if (!item)
            continue;

        if ( !item->CheckRequirements(owner,msg) || !HasWeight(0) || !HasSpace(0) )
        {
            int slot = item->GetLocInParent();
            psItem* removed = RemoveEquipment(slot);

            if (removed)
            {
                if (msg != "None")
                {
                    psserver->SendSystemInfo(cnum,"You can no longer keep %s equipped. %s",
                                             removed->GetName(), msg.GetData() );
                }

                psserver->GetCharManager()->SendOutEquipmentMessages( cnum, slot, removed, psEquipmentMessage::DEEQUIP );
                needToUpdateInv = true;

                // Try to deequip
                if ( PutInBulk(removed) )
                    continue;

                // Not any space left there either, drop on ground
                owner->DropItem(removed,removed->IsTransient());
                droppedSomething = true;
            }
        }
    }

    if (needToUpdateInv)
        psserver->GetCharManager()->SendInventory( cnum );
    if (droppedSomething)
        psserver->SendSystemError(cnum,"Your inventory is full!");
}


bool psCharacterInventory::Load()
{
    return Load(owner->GetCharacterID());
}


bool psCharacterInventory::Load(unsigned int use_id)
{    
    doRestrictions = (owner->GetCharType() == PSCHARACTER_TYPE_PLAYER);

    Result items(db->Select("SELECT * from item_instances where char_id_owner=%u",use_id));
    if ( items.IsValid() )
    {
        int count = items.Count();
        psItemSet* set = new psItemSet;

        for ( int i = 0; i < count; i++ )
        {
            unsigned int stats_id = items[i].GetUInt32("item_stats_id_standard");
            psItemStats *stats = CacheManager::GetSingleton().GetBasicItemStatsByID(stats_id);
            if ( !stats )
            {
                Bug3("Error! Item %s could not be loaded. Check item_stats id=%d\n",items[i]["id"], stats_id);
                continue;
            }
            
            psItem* item;
            if (stats->GetIsGlyph())
            {
                item = new psGlyph();
            }
            else
            {
                item = new psItem();
            }
            uint32 parentid;
            if ( !item->Load(items[i],parentid) )
            {
                Bug1("Error! Item could not be loaded. Skipping.\n");
                delete item;
                continue;
            }
            
            if ( !PlaceLoadedItem(items[i]["location"],items[i]["equipped_slot"],item) )
            {
                Bug5("Item %s(%u) could not be loaded for %s(%d). Skipping this item.\n",
                     item->GetName(), item->GetUID(), owner->GetCharName(), owner->GetCharacterID() );
                delete item;
                continue;
            }
            set->Add(item,parentid);
        }

        set->ResolveAllParents();

        for (size_t i=0; i < set->GetSize(); i++)
            if (set->Get(i))
                set->Get(i)->SetLoaded();

        loaded = true; // Begin dimension checking
        ReassessInventoryDimensions();

        set->Release();
        delete set;
        return true;
    }
    else
        return false;    
}    


bool psCharacterInventory::QuickLoad(unsigned int use_id)
{    
    Result items(db->Select("SELECT id, item_stats_id_standard, equipped_slot FROM item_instances WHERE (char_id_owner=%u AND location='E')",use_id));
    if ( items.IsValid() )
    {
        for ( int i=0; i < (int)items.Count(); i++ )
        {
            unsigned int stats_id = items[i].GetUInt32("item_stats_id_standard");
            psItemStats *stats = CacheManager::GetSingleton().GetBasicItemStatsByID(stats_id);
            if ( !stats )
            {
                Bug2("Error! Item %s could not be loaded. Skipping.\n", items[i]["id"] );
                continue;
            }

            // Quick load; we just need to know what it looks like
            psItem* item = stats->InstantiateBasicItem();

            if ( !PlaceLoadedItem("E",items[i]["equipped_slot"],item) )
            {
                Bug5("Item %s(%s) could not be quick loaded for %s(%d). Skipping this item.\n",
                     item->GetName(), items[i]["id"], owner->GetCharName(), owner->GetCharacterID() );
                delete item;
                continue;
            }
        }

        // Items are not flagged as 'loaded', and can not be saved
        return true;
    }
    else
        return false;
}


bool psCharacterInventory::PlaceLoadedItem(const char *loc, const char *slot, psItem * & item)
{
    if (loc == NULL)
    {
        csString error;
        error.Format("Item %s(%u) Could not be placed in %s(%d) inventory into slot %s because it's location in parent was NULL", item->GetName(), item->GetUID(), owner->GetCharName(), owner->GetCharacterID(), slot ); 
        Bug1(error);
        return false;
    }

    csString location(loc);
    // Location should be either EQUIPED, INVENTORY or BULK
    // This item is equiped in a character equipment slot (weapon, armor)
    if (location == "E")
    {
        int eslot = CacheManager::GetSingleton().slotName.GetID( slot );
                
        if (!EquipIn(eslot,item))
        {
            csString error;
            error.Format("\nCharacter: %s(%d)\nLocation: %s Slot: %s\nItem: %s(%u)\n", owner->GetCharName(), owner->GetCharacterID(),loc, slot, item->GetName(), item->GetUID() );            
            Error1( error );

            // Item may have been equippable due to a buff
            // Attempt to preserve this item by moving it to bulk
            return PutInBulk(item,ANY_BULK_SLOT);
        }
    }

    // This item is in the player's BULK slots.
    else if (location == "I")
    {
        // Find out which slot and put it there.
        if ( !PutInBulk(item,item->GetLocInParent()) )
        {
            // An equipped item may have stolen it's slot, so we try any
            if( !PutInBulk(item,ANY_BULK_SLOT) )
            {
                // Could not put the item in the slot.  This means the index was out of range or an item is
                // already in the slot.
                // An examination of the player's inventory should show which is the case.
                csString error;
                error.Format("\nCharacter: %s(%d)\nLocation: %s Slot: %d\nItem: %s(%u)\nError:%s\n", 
                             owner->GetCharName(), owner->GetCharacterID(),loc, item->GetLocInParent(), item->GetName(),
                             item->GetUID(),lastError.GetDataSafe());
                Error1( error );
                
                return false;
            }
        }
    }

    else if (location == "C")
    {
        // Do nothing
    }
    else
    {
        Error4("Item ID %u in inventory of character id %u has unknown location %s.",item->GetUID(),owner->GetCharacterID(),loc);
        return false;
    }

    return true;
}


bool psCharacterInventory::PutInBulk(psItem * & item, int slot)
{
    if (slot<ANY_EMPTY_BULK_SLOT || slot>=PSCHARACTER_BULK_COUNT)
    {
        lastError.Format("Bulkslot %d is out of bounds!", slot );
        return false;
    }

    if (item==NULL)
    {
        lastError.Format("Item is null!");
        return false;
    }    
    
    // Can it fit here?
    if ( !item->FitsInSlots(PSITEMSTATS_SLOT_BULK) )
    {
        lastError.Format("%s does not fit in this slot", item->GetName() );
        return false;
    }

    if ( !HasWeight(item->GetSumWeight()) )
    {
        lastError.Format("The %s is too heavy for you to carry", item->GetName() );
        return false;
    }        
    
    if ( !HasSpace(item->GetSumSize()) )
    {
        lastError.Format("The %s is too big for you to carry", item->GetName() );    
        return false;
    }

    /** If we were passed ANY_BULK_SLOT or ANY_EMPTY_BULK_SLOT we need to scan the possible
     * slots and try to find a valid place to put this item.  (non-negative slot number)
     * Otherwise, we'll just skip over these and attempt to place in the given slot.  If
     * that fails, we'll return false and possibly call this again specifying ANY_BULK_SLOT.
     */
    int i;

    // First see if we can stack this with an existing stack in inventory
    if (slot == ANY_BULK_SLOT && item->GetIsStackable())
    {
        for (i=0; i<PSCHARACTER_BULK_COUNT; i++)
        {
            if (bulk[i] && bulk[i]->CheckStackableWith(item))
            {
                slot = i;
                break;
            }
        }
    }

    // Next check the main bulk slots
    if (slot < 0)
    {
        for (i=0; i<PSCHARACTER_BULK_COUNT; i++)
        {
            if (bulk[i] == NULL)
            {
                slot = i;
                break;
            }
        }
    }

    // Now try looking for a container
    if (slot < 0)
    {
        for (i=0; i<PSCHARACTER_BULK_COUNT; i++)
        {
            if (bulk[i] && bulk[i]->GetIsContainer())
            {
                slot = i;
                break;
            }
        }
    }

    if (slot < 0)
    {
        lastError.Format("Cannot find slot to put %s into", item->GetName() );
        return false; // Item does not fit
    }        

    item->ItemAboutToMove();

    if (bulk[slot] == NULL) // Empty slot
    {
        bulk[slot] = item;
        item->SetLocInParent(slot,false);
        item->SetOwningCharacter(owner, false);

        item->Save();
        AddDimensionsToInventory(item);
    }
    else if (bulk[slot]->GetIsContainer()) // Container
    {
        if ( !bulk[slot]->AddItemToContainer(item) )
            return false;

        AddDimensionsToInventory(item);
    }
    else // Existing stack
    {
        item->SetOwningCharacter(owner, false);

        AddDimensionsToInventory(item);

        /* We presume that CombineStack will return the passed pointer if a full combining
         * can't be completed.  This is true now, and we'll assert if it becomes not true
         * so we can alter the return from this function in the future if need be.
         */
        psItem* remainder = bulk[slot]->CombineStack(item);

        if (remainder) // Handle a remainder during the stacking attempt
        {
            SubtractDimensionsFromInventory(item);  // Nope, didn't work; take it back

            CS_ASSERT(remainder==item);
            /* Return false which will let the caller know that the psItem it wanted to
             * place still needs to be placed or destroyed.  In this case its stack count
             * may have been modified, but that doesn't matter
             * - the remainder still has to be placed.
             */
            return false;
        }
        else // Stacked
        {
            item = bulk[slot]; // Point to new location
            item->Save();
        }
    }

    return true;
}


EQUIPMENTDATA_TYPE & psCharacterInventory::GetEquipmentObject(int slot)
{
    return equipment[slot];
}


psItem *psCharacterInventory::GetEquipmentItem(int slot)
{
    if (slot<0 || slot>=PSCHARACTER_SLOT_COUNT)
        return NULL;
    return equipment[slot].item;
}


psItem *psCharacterInventory::RemoveEquipment(int slot)
{
    if (slot<0 || slot>=PSCHARACTER_SLOT_COUNT)
        return NULL;

    psItem* currentitem=equipment[slot].item;
    if (currentitem==NULL)
        return NULL;

    if (currentitem->IsInUse())
        return NULL;

    equipment[slot].item=NULL;
    // currentitem->SetOwningCharacter(NULL, true);

    SubtractDimensionsFromInventory(currentitem);
    owner->CalculateEquipmentModifiers();

    return currentitem;
}

bool psCharacterInventory::PutInEquipped(int slot, psItem * item)
{
    if (slot<0 || slot>=PSCHARACTER_SLOT_COUNT)
        return false;

    if (equipment[slot].item == NULL || item == NULL)
        return false;
        
    if ( equipment[slot].item->GetIsContainer() )
    {
        if ( !equipment[slot].item->AddItemToContainer(item) )
            return false;

        AddDimensionsToInventory(item);
        return true;
    }

    return false;
}

bool psCharacterInventory::EquipIn(int slot, psItem * item)
{
    if (slot<0 || slot>=PSCHARACTER_SLOT_COUNT)
        return false;

    /* Do not put an item into a slot that is already occupied - doing so will lose a reference to the item.
    *  Even if it wouldn't, it's a bug!  The old item should be removed first.
    */
    if (equipment[slot].item!=NULL || item==NULL)
        return false;

    csString resp;
    if ( !item->CheckRequirements( owner, resp ) )
    {
        // If this is a PC, try to preseve the item
        if (owner->IsNPC())
        {
            // Inform that NPC failed to equip item
            Error4("On loading: NPC  %s (%d) tried to equip %s but failed the item Requirements:",
                   owner->GetCharFullName(),
                   owner->GetCharacterID(),
                   item->GetName()
                   );
        }

        if (owner->GetActor())
            psserver->SendSystemInfo(owner->GetActor()->GetClientID(),resp);

        // It is ALWAYS the caller's responsibility to dispose the item.
        return false;
    }
                
    equipment[slot].item=item;
    item->SetOwningCharacter(owner, false);
    item->SetLocInParent(slot, true);
    
    AddDimensionsToInventory(item);
    owner->CalculateEquipmentModifiers();
    return true;
}


psItem *psCharacterInventory::GetBulkItem(int bulkslot)
{
    if (bulkslot<0 || bulkslot>=PSCHARACTER_BULK_COUNT)
        return NULL;
    return bulk[bulkslot];
}


psItem *psCharacterInventory::RemoveBulk(int bulkslot, int count )
{
    if (bulkslot<0 || bulkslot>=PSCHARACTER_BULK_COUNT)
        return NULL;

    psItem* currentItem = bulk[bulkslot];

    if (currentItem==NULL)
        return NULL;

    // default -1 is to drop all in stack.
    if ( count == -1 ) count = currentItem->GetStackCount();

    if (count<0  ||  count > currentItem->GetStackCount())
        return NULL;
    
    // Remove ALL items in stack
    if ( count == currentItem->GetStackCount() )
    {
        // currentItem->SetOwningCharacter(NULL, true);  // Disabled due to Save() assert, KWF
        
        bulk[bulkslot] = NULL;

        SubtractDimensionsFromInventory(currentItem);
        return currentItem;
    }
    else
    {
        psItem* newItem = currentItem->SplitStack((unsigned short)count);
        if (!newItem)
            return NULL;

        SubtractDimensionsFromInventory(newItem);
        return newItem;
    }
}

void psCharacterInventory::RunEquipScripts()
{
    for (int equipslot=0;equipslot<INVENTORY_EQUIP_COUNT;equipslot++)
    {
        if (equipment[equipslot].item!=NULL)
        {
            csString script = equipment[equipslot].item->GetBaseStats()->GetProgressionEventEquip();
            if ( script.Length() > 0 )
            {
                gemActor *actor = owner->GetActor();
                psserver->GetProgressionManager()->ProcessEvent(script,  actor );
            }
        }
    }
}
 

unsigned int psCharacterInventory::TotalStackOfItem(psItemStats* item)
{
    unsigned int count = 0;
    for (int bulkslot=0;bulkslot<PSCHARACTER_BULK_COUNT;bulkslot++)
    {
        if (bulk[bulkslot]!=NULL)
        {
            psItem* inside = bulk[bulkslot];
            if(inside->GetBaseStats() == item)
                count += (unsigned int)inside->GetStackCount();
        }
    }
    
    for (int equipslot=0;equipslot<INVENTORY_EQUIP_COUNT;equipslot++)
    {
        if (equipment[equipslot].item!=NULL)
        {
            psItem* inside = equipment[equipslot].item;
            if(inside->GetBaseStats() == item)
                count += (unsigned int)inside->GetStackCount();
        }
    }

    return count;
}


psItem *psCharacterInventory::FindInBulk(uint32 itemID)
{
    for (int bulkslot = 0; bulkslot < PSCHARACTER_BULK_COUNT; bulkslot++)
    {
        if (bulk[bulkslot]!=NULL)
        {
            if (bulk[bulkslot]->GetUID() == itemID)
                return bulk[bulkslot];
        }
    }
    return NULL;
}


bool psCharacterInventory::HaveKeyForLock(uint32 lock)
{
    if (equipment[PSCHARACTER_SLOT_LEFTHAND].item && equipment[PSCHARACTER_SLOT_LEFTHAND].item->CanOpenLock(lock))
        return true;
    if (equipment[PSCHARACTER_SLOT_RIGHTHAND].item && equipment[PSCHARACTER_SLOT_RIGHTHAND].item->CanOpenLock(lock))
        return true;
    for (int bulkslot = 0; bulkslot < PSCHARACTER_BULK_COUNT; bulkslot++)
    {
        if (bulk[bulkslot]!=NULL)
        {
            if (bulk[bulkslot]->CanOpenLock(lock))
                return (bulk[bulkslot] ? true : false);
        }

    }
    return false;
}

bool psCharacterInventory::Attackable(int slot)
{
    // Slot out of range
    if (slot<0 || slot>=PSCHARACTER_SLOT_COUNT)
        return false;
    // TODO:  Should the combat/peace toggle affect this?

    // The autoattack or singleattack flags must be set
    if ((equipment[slot].EquipmentFlags & PSCHARACTER_EQUIPMENTFLAG_AUTOATTACK) ||
        (equipment[slot].EquipmentFlags & PSCHARACTER_EQUIPMENTFLAG_SINGLEATTACK))
    {
        // Check if the slot is empty and can attack when empty
        if (equipment[slot].item==NULL && (equipment[slot].EquipmentFlags & PSCHARACTER_EQUIPMENTFLAG_ATTACKIFEMPTY))
            return true;
        // Otherwise the slot must have an item in it
        if (equipment[slot].item==NULL)
            return false;
        // If the item is a melee weapon, it's OK
        if (equipment[slot].item->GetIsMeleeWeapon())
            return true;
        if (equipment[slot].item->GetIsRangeWeapon())
        {

            // Ranged weapons not supported right now
            /*
            // Check for ammo usage
            if (!inventory.equipment[slot].item->GetUsesAmmo())
            return true;

            // Find some ammo - first check the same slot
            if (!inventory.equipment[slot].item->GetIsAmmo())
            return true;

            // Check other equipment slots
            for (int ammocheck=0;ammocheck<PSCHARACTER_SLOT_COUNT;ammocheck++)
            {
            if (inventory.equipment[ammocheck].item->GetIsAmmo() &&
            inventory.equipment[ammocheck].item->GetAmmoType() == inventory.equipment[slot].item->GetAmmoType() &&
            inventory.equipment[ammocheck].item->GetStackCount()>0)
            return true;
            }

            // TODO: Check inventory
            */
        }
    }

    return false;
}


bool psCharacterInventory::AutoAttackable(int slot)
{
    // Slot out of range
    if (slot<0 || slot>=PSCHARACTER_SLOT_COUNT)
        return false;

    return (equipment[slot].EquipmentFlags & PSCHARACTER_EQUIPMENTFLAG_AUTOATTACK);
}


bool psCharacterInventory::SingleAttackable(int slot)
{
    // Slot out of range
    if (slot<0 || slot>=PSCHARACTER_SLOT_COUNT)
        return false;

    return (equipment[slot].EquipmentFlags & PSCHARACTER_EQUIPMENTFLAG_SINGLEATTACK) ? true : false;
}


psItem *psCharacterInventory::GetEffectiveWeaponInSlot(int slot)
{
    // Slot out of range
    if (slot<0 || slot>=PSCHARACTER_SLOT_COUNT)
        return NULL;

    // if there is a weapon in the slot, return it
    if (equipment[slot].item!=NULL)
    {
        if (equipment[slot].item->GetIsMeleeWeapon() ||
            equipment[slot].item->GetIsRangeWeapon())
            return equipment[slot].item;
        else
            return NULL;
    }
    
    // right hand and left hand can attack also if no weapon is there
    // the default_if_empty is the fist weapon
    if (equipment[slot].EquipmentFlags & PSCHARACTER_EQUIPMENTFLAG_ATTACKIFEMPTY)
        return equipment[slot].default_if_empty;

    return NULL;
}


psItem *psCharacterInventory::GetEffectiveArmorInSlot(int slot)
{
    // Slot out of range
    if (slot<0 || slot>=PSCHARACTER_SLOT_COUNT)
        return NULL;

    // if there is an item in the slot, return it
    if (equipment[slot].item!=NULL)
        return equipment[slot].item;

    // hit armor locations should use the base armor if none is present
    // the default_if_empty is the basecloth armor
    return equipment[slot].default_if_empty;
}


int psCharacterInventory::FindItemInTopLevelEquipment(psItem *item)
{
    for (int i=0;i<PSCHARACTER_SLOT_COUNT;i++)
    {
        if (equipment[i].item==item)
            return i;
    }
    return -1;
}


int psCharacterInventory::FindItemInTopLevelBulk(psItem *item)
{
    for (int i=0;i<PSCHARACTER_BULK_COUNT;i++)
    {
        if (bulk[i]==item)
            return i;
    }
    return -1;
}


int psCharacterInventory::FindItemInTopLevelBulkWithStats(psItemStats *stats)
{
    for (int i=0;i<PSCHARACTER_BULK_COUNT;i++)
    {
        if (bulk[i] && bulk[i]->GetCurrentStats() == stats)
            return i;
    }
    return -1;
}


int psCharacterInventory::FindItemInTopLevelEquipmentWithStats(psItemStats *stats)
{
    for (int i=0;i<PSCHARACTER_SLOT_COUNT;i++)
    {
        if (equipment[i].item && equipment[i].item->GetCurrentStats() == stats)
            return i;
    }
    return -1;
}


bool psCharacterInventory::HasSpace( float requiredSpace )
{
    if ( !doRestrictions )
        return true;

#if !ENABLE_MAX_CAPACITY
    return true;
#endif

    return (totalSize + requiredSpace <= maxSize );
}

bool psCharacterInventory::HasWeight( float requiredWeight ) 
{ 
    if ( !doRestrictions )
        return true;

    return (totalWeight + requiredWeight <= maxWeight );
}    

size_t psCharacterInventory::CanFit(psItem* item)
{
    if ( !doRestrictions )
        return 65535; // Fit any ammount

    if ( !HasWeight(0) || !HasSpace(0) )
        return 0; // Can't fit any

    size_t w = 65535;
    size_t s = 65535;

    float individualWeight = item->GetBaseStats()->GetWeight();
    if ( individualWeight > 0.01 )
        w = (size_t)((maxWeight-totalWeight)/individualWeight);

#if ENABLE_MAX_CAPACITY
    unsigned short individualSize = item->GetBaseStats()->GetSize();
    if ( individualSize > 0 )
        s = (size_t)((maxSize-totalSize)/individualSize);
#endif

    return (w<s)?w:s; // Capacity depends on the lesser
}

void psCharacterInventory::ReassessInventoryDimensions()
{
    if (!loaded) // Make sure we're ready to calculate
        return;

    int i;

    totalCount = 0;
    totalWeight = 0.0f;
    totalSize = 0.0f;

    for (i=0; i<PSCHARACTER_SLOT_COUNT; i++)
    {
        if (equipment[i].item != NULL)
            AddDimensionsToInventory(equipment[i].item);
    }

    for (i=0; i<PSCHARACTER_BULK_COUNT; i++)
    {
        if (bulk[i] != NULL)
            AddDimensionsToInventory(bulk[i]);
    }
}

void psCharacterInventory::AddDimensionsToInventory(psItem *item)
{
    if (loaded) // Make sure we're ready to calculate
    {
        totalCount += item->GetStackCount(); // TODO: count children
        totalWeight += item->RecalculateFullWeight(); // Total weight of stack including children
        if (item->GetParentItem() == NULL) // Sizes may be nested; don't add children to the total
            totalSize += item->GetSumSize(); // Total size of all in this stack
    }
}

void psCharacterInventory::SubtractDimensionsFromInventory(psItem *item)
{
    if (loaded) // Make sure we're ready to calculate
    {
        totalCount -= item->GetStackCount(); // TODO: count children
        totalWeight -= item->RecalculateFullWeight(); // Total weight of stack including children
        if (item->GetParentItem() == NULL) // Sizes may be nested; don't add children to the total
            totalSize -= item->GetSumSize(); // Total size of all in this stack
    }
}

int psCharacterInventory::HasEmptyBulkSlots()
{
    int count = 0;

    for (int i=0; i<PSCHARACTER_BULK_COUNT; i++)
    {
        if (bulk[i] == NULL)
            ++count;
    }

    return count;
}

void psCharacterInventory::SetDoRestrictions(bool v)
{
    if (doRestrictions == v)
        return;

    doRestrictions = v;

    // Reassess dimentions and drop items to meet them, if needed
    if (doRestrictions)
        CalculateStats();
}

//-----------------------------------------------------------------------------
/*
WorkInformation::WorkInformation()
{
    Reset();
}

void WorkInformation::Reset()
{
    workItem  = 0;
    patternId = 0;
    groupPatternId = 0;
    patternKFactor = 0.0;
}
*/

//-----------------------------------------------------------------------------

// Definition of the itempool for psCharacter(s)
PoolAllocator<psCharacter> psCharacter::characterpool;

const char * psCharacter::player_mode_to_str[] =
 {"unknown","peace","combat","spell casting","working","dead","sitting","exhausted"};

void *psCharacter::operator new(size_t allocSize)
{
// Debug3(LOG_CHARACTER,"%i %i", allocSize,sizeof(psCharacter));
//    CS_ASSERT(allocSize<=sizeof(psCharacter));
    return (void *)characterpool.CallFromNew();
}

void psCharacter::operator delete(void *releasePtr)
{
    characterpool.CallFromDelete((psCharacter *)releasePtr);
}

psCharacter::psCharacter() :
    guildinfo(NULL), accountid(0), npc_masterid(0),
    powerScript(NULL), maxRealmScript(NULL),
    attributes(this), modifiers(this), skills(this), loaded(false)
{
    characterType = PSCHARACTER_TYPE_UNKNOWN;

    helmGroup = "";
    help_event_flags = 0;
    duel_points = 0;
    situation_wum = 0.0f;
    effect_wum = 0.0f;
    memset(advantage_bitfield,0,sizeof(advantage_bitfield));
    characterid = 0;
    familiar_id = 0;
    owner_id = 0;
    animal_affinity = "";

    override_max_hp = 0.0f;
    override_max_mana = 0.0f;

    inventory.SetOwner(this);
    memset(&money,0,sizeof(money));

    name = lastname = fullname = " ";
    SetSpouseName( "" );
    isMarried = false;

    raceinfo = NULL;
    combat_stance = PSCHARACTER_STANCE_NORMAL;
    vitals = new psServerVitals(this);
//    workInfo = new WorkInformation();
    
    loot_category_id = 0;
    loot_money = 0;

    location.loc_sector = NULL;
    location.loc_x = 0.0f;
    location.loc_y = 0.0f;
    location.loc_z = 0.0f;
    location.loc_yrot = 0.0f;
    spawn_loc = location;

    for (int i=0;i<PSTRAIT_LOCATION_COUNT;i++)
        traits[i] = NULL;

    npc_spawnruleid = -1;

    tradingStopped = false;
    tradingStatus = psCharacter::NOT_TRADING;
    merchant = NULL;
    trainer = NULL; 
    actor = NULL;
    merchantInfo = NULL;
    trainerInfo = NULL;

    timeconnected = 0;
    startTimeThisSession = csGetTicks();

    KFactor = 1.0;
    spellCasting = NULL;

//    transformation = NULL;
    work_state = PSCHARACTER_WORKSTATE_HALTED;
    workEvent = NULL;
    kill_exp = 0;
    impervious_to_attack = 0;

    progressionScript.Clear();
    faction_standings = "";
    
    attackValueModifier = 1;
    defenseValueModifier = 1;
    meleeDefensiveDamageModifier = 1;
    
    player_mode = PSCHARACTER_MODE_PEACE;
    lastError = "None";

    staminaCalc = psserver->GetMathScriptEngine()->FindScript("StaminaBase");
    if( !staminaCalc)
        Warning1(LOG_CHARACTER, "Can't find math script StaminaBase!");
}

psCharacter::~psCharacter()
{
    if (guildinfo)
        guildinfo->Disconnect(this);

    inventory.Clear();

    // First force and update of the DB of all QuestAssignments before deleting
    // every assignment.
    UpdateQuestAssignments(true);
    while (assigned_quests.Length() )
        delete assigned_quests.Pop() ;
        
    delete vitals;
    vitals = NULL;
//    delete workInfo;
}

void psCharacter::SetActor( gemActor* newActor )
{
    actor = newActor;
    if (actor)
        inventory.RunEquipScripts();
}

bool psCharacter::Load(iResultRow& row)
{
    // TODO:  Link in account ID?
    SetCharacterID(row.GetInt("id"));
    SetAccount(row.GetInt("account_id"));
    SetCharType( row.GetUInt32("character_type") );

    SetFullName(row["name"], row["lastname"]);
    SetOldLastName( row["old_lastname"] );

    unsigned int raceid = row.GetUInt32("racegender_id");
    psRaceInfo *raceinfo = CacheManager::GetSingleton().GetRaceInfoByID(raceid);
    if (!raceinfo)
    {
        Error3("Character ID %s has unknown race id %s.",row["id"],row["racegender_id"]);
        return false;
    }
    SetRaceInfo(raceinfo);

    //Assign the Helm Group
    Result helmResult(db->Select("SELECT helm FROM race_info WHERE race_id=%d", raceid));
    helmGroup = helmResult[0]["helm"];
                    
    SetDescription(row["description"]);

    attributes.SetStat(PSITEMSTATS_STAT_STRENGTH,(unsigned int)row.GetFloat("base_strength"), false);
    attributes.SetStat(PSITEMSTATS_STAT_AGILITY,(unsigned int)row.GetFloat("base_agility"), false);    
    attributes.SetStat(PSITEMSTATS_STAT_ENDURANCE,(unsigned int)row.GetFloat("base_endurance"), false);
    attributes.SetStat(PSITEMSTATS_STAT_INTELLIGENCE,(unsigned int)row.GetFloat("base_intelligence"), false);
    attributes.SetStat(PSITEMSTATS_STAT_WILL,(unsigned int)row.GetFloat("base_will"), false);
    attributes.SetStat(PSITEMSTATS_STAT_CHARISMA,(unsigned int)row.GetFloat("base_charisma"), false);

    // NPC fields here
    npc_spawnruleid = row.GetUInt32("npc_spawn_rule");
    npc_masterid    = row.GetUInt32("npc_master_id");

    // This substitution allows us to make 100 orcs which are all copies of the stats, traits and equipment
    // from a single master instance.
    uint32_t use_id = (npc_masterid)?npc_masterid:characterid;

    SetHitPointsMax(row.GetFloat("base_hitpoints_max"));
    override_max_hp = GetHitPointsMax();
    SetManaMax(row.GetFloat("base_mana_max"));
    override_max_mana = GetManaMax();

     if (!LoadSkills(use_id))
    {
        Error2("Cannot load skills for Character ID %u.",characterid);
        return false;
    }

    RecalculateStats();

    SetHitPoints(row.GetFloat("mod_hitpoints"));
    SetMana(row.GetFloat("mod_mana"));
    SetStamina(row.GetFloat("stamina_physical"),true);
    SetStamina(row.GetFloat("stamina_mental"),false);

    vitals->SetOrigVitals(); // This saves them as loaded state for restoring later without hitting db, npc death resurrect.

    lastlogintime = row["last_login"];
    faction_standings = row["faction_standings"];
    csString script = row["progression_script"];
    if (script != "")
        progressionScript.Format("<evt>%s</evt>",script.GetData());

    SetMoney(psMoney(
        row.GetInt("money_circles"),
        row.GetInt("money_octas"),
        row.GetInt("money_hexas"),
        row.GetInt("money_trias")));

    psSectorInfo *sectorinfo=CacheManager::GetSingleton().GetSectorInfoByID(row.GetUInt32("loc_sector_id"));
    if (sectorinfo==NULL)
    {
        Error3("Character ID %u has unresolvable sector id %u.",characterid,row.GetUInt32("loc_sector_id"));
        return false;
    }

    SetLocationInWorld(sectorinfo,
        row.GetFloat("loc_x"),
        row.GetFloat("loc_y"),
        row.GetFloat("loc_z"),
        row.GetFloat("loc_yrot") );
    spawn_loc = location;

    // Guild fields here
    guildinfo = CacheManager::GetSingleton().FindGuild(row.GetUInt32("guild_member_of"));
    if (guildinfo)
        guildinfo->Connect(this);

    // Loot rule here
    loot_category_id = row.GetInt("npc_addl_loot_category_id");

    impervious_to_attack = (row["npc_impervious_ind"][0]=='Y') ? ALWAYS_IMPERVIOUS : 0;

    duel_points = row.GetFloat("duel_points");

    // Familiar Fields here
    animal_affinity  = row[ "animal_affinity" ];
    //owner_id         = row.GetUInt32( "owner_id" ); 
    help_event_flags = row.GetUInt32("help_event_flags");

    if (!LoadTraits(use_id))
    {
        Error2("Cannot load traits for Character ID %u.",characterid);
        return false;
    }

    if (!LoadAdvantages(use_id))
    {
        Error2("Cannot load advantages for Character ID %u.",characterid);
        return false;
    }

    // This data is loaded only if it's a player, not an NPC
    if ( !IsNPC() )
    {
      if (!LoadQuestAssignments())
      {
          Error2("Cannot load quest assignments for Character ID %u.",characterid);
          return false;
      }

    }

    if (npc_masterid && use_id != npc_masterid )
    {
        // also load character specific items
        if (!inventory.Load(characterid))
        {
            Error2("Cannot load character specific items for Character ID %u.",characterid);
            return false;
        }
    }
    else
    {
        inventory.Load();
    }

    if ( !LoadRelationshipInfo( characterid ) ) // Buddies, Marriage Info, Familiars
    {
        return false;
    }

    // Load merchant info
    csRef<psMerchantInfo> merchant = csPtr<psMerchantInfo>(new psMerchantInfo());
    if (merchant->Load(use_id))
    {
        merchantInfo = merchant;
    }

    // Load trainer info
    csRef<psTrainerInfo> trainer = csPtr<psTrainerInfo>(new psTrainerInfo());
    if (trainer->Load(use_id))
    {
        trainerInfo = trainer;
    }

    if (!LoadSpells(use_id))
    {
        Error2("Cannot load spells for Character ID %u.",characterid);
        return false;
    }

    timeconnected        = row.GetUInt32("time_connected_sec");
    startTimeThisSession = csGetTicks();

    // Load Experience Points W and Progression Points X
    SetExperiencePoints(row.GetUInt32("experience_points"));
    SetProgressionPoints(row.GetUInt32("progression_points"),false);

    // Load the kill exp
    kill_exp = row.GetUInt32("kill_exp");

    // Load the math script
    powerScript = psserver->GetMathScriptEngine()->FindScript("CalculatePowerLevel");
    if ( !powerScript )
    {
        Warning1(LOG_CHARACTER, "Can't find math script CalculatePowerLevel!");        
        return false;
    }
    maxRealmScript = psserver->GetMathScriptEngine()->FindScript("MaxRealm");
    if ( !maxRealmScript )
    {
        Warning1(LOG_CHARACTER, "Can't find math script MaxRealm!");        
        return false;
    }

    loaded = true;
    return true;
}

bool psCharacter::QuickLoad(iResultRow& row)
{
    SetCharacterID(row.GetInt("id"));
    SetFullName(row["name"], row["lastname"]);

    unsigned int raceid = row.GetUInt32("racegender_id");
    psRaceInfo *raceinfo = CacheManager::GetSingleton().GetRaceInfoByID(raceid);
    if (!raceinfo)
    {
        Error3("Character ID %s has unknown race id %s.",row["id"],row["racegender_id"]);
        return false;
    }
    Result helmResult(db->Select("SELECT helm FROM race_info WHERE race_id=%d", raceid));
    helmGroup = helmResult[0]["helm"];
    
    SetRaceInfo(raceinfo);

    if (!LoadTraits(characterid))
    {
        Error2("Cannot load traits for Character ID %u.",characterid);
        return false;
    }

    // Load equipped items
    inventory.QuickLoad(characterid);

    return true;
}

bool psCharacter::LoadRelationshipInfo( unsigned int characterid )
{

    Result has_a( db->Select( "SELECT a.*, b.name AS 'buddy_name' FROM character_relationships a, characters b WHERE a.related_id = b.id AND a.character_id = %u", characterid ) );
    Result of_a( db->Select( "SELECT a.*, b.name AS 'buddy_name' FROM character_relationships a, characters b WHERE a.character_id = b.id AND a.related_id = %u", characterid ) );

    if ( !LoadFamiliar( has_a, of_a ) )
    {
        Error2("Cannot load familiar info for Character ID %u.",characterid);
        return false;
    }

    if ( !LoadMarriageInfo( has_a ) )
    {
      Error2("Cannot load Marriage Info for Character ID %u.",characterid);
      return false;
    }

    if ( !LoadBuddies( has_a, of_a ) )
    {
        Error2("Cannot load buddies for Character ID %u.",characterid);
        return false;
    }
    return true;
}

bool psCharacter::LoadBuddies( Result& myBuddies, Result& buddyOf )
{    
    unsigned int x;

    if ( !myBuddies.IsValid() )
      return true;

    for ( x = 0; x < myBuddies.Count(); x++ )
    {        

        if ( strcmp( myBuddies[x][ "relationship_type" ], "buddy" ) == 0 )
        {
            Buddy newBud;
            newBud.name = myBuddies[x][ "buddy_name" ];
            newBud.playerID = myBuddies[x].GetUInt32( "related_id" ); 

            buddyList.Insert( 0, newBud );
        }
    }
    
    
    // Load all the people that I am a buddy of. This is used to inform these people
    // of when I log in/out.

    for (x = 0; x < buddyOf.Count(); x++ )
    {
        if ( strcmp( buddyOf[x][ "relationship_type" ], "buddy" ) == 0 )
        {
            buddyOfList.Insert( 0, buddyOf[x].GetUInt32( "character_id" ) );
        }
    }
     
    return true;                  
}

bool psCharacter::LoadMarriageInfo( Result& result)
{
    //Result result( db->Select("SELECT * FROM character_marriage_details"
    //                          " WHERE character_id=%d", characterid));
    if ( !result.IsValid() )
    {
        Error3("Could not load marriage info for character %d. Error was: %s", characterid, db->GetLastError() );
        return false;
    }

    for ( unsigned int x = 0; x < result.Count(); x++ )
    {        

        if ( strcmp( result[x][ "relationship_type" ], "spouse" ) == 0 )
        {
            const char* spouseName = result[x]["spousename"];
            if ( spouseName == NULL )
                return true;
            
            SetSpouseName( spouseName );
            
            Notify2( LOG_MARRIAGE, "Successfully loaded marriage info for %s", name.GetData() );
            break;
        }
    }

    return true;
}

bool psCharacter::LoadFamiliar( Result& pet, Result& owner )
{
    familiar_id = 0;
    owner_id = 0;

    if ( !pet.IsValid() )
    {
        Error3("Could not load pet info for character %d. Error was: %s", characterid, db->GetLastError() );
        return false;
    }

    if ( !owner.IsValid() )
    {
        Error3("Could not load owner info for character %d. Error was: %s", characterid, db->GetLastError() );
        return false;
    }

    unsigned int x;
    for ( x = 0; x < pet.Count(); x++ )
    {        

        if ( strcmp( pet[x][ "relationship_type" ], "familiar" ) == 0 )
        {
            familiar_id = pet[x].GetInt( "related_id" );
            Notify2( LOG_MARRIAGE, "Successfully loaded familair for %s", name.GetData() );
            break;
        }
    }

    for ( x = 0; x < owner.Count(); x++ )
    {        

        if ( strcmp( owner[x][ "relationship_type" ], "familiar" ) == 0 )
        {
            owner_id = owner[x].GetInt( "character_id" );
            Notify2( LOG_MARRIAGE, "Successfully loaded owner for %s", name.GetData() );
            break;
        }
    }

    return true;
}

void psCharacter::SetLastLoginTime(const char *last_login, bool save )
{
    csString timeStr;

    if ( !last_login )
    {
        time_t curr=time(0);
        tm* localtm = localtime(&curr);

        timeStr.Format("%d-%02d-%02d %02d:%02d:%02d",
                        localtm->tm_year+1900,
                        localtm->tm_mon+1,
                        localtm->tm_mday,
                        localtm->tm_hour,
                        localtm->tm_min,
                        localtm->tm_sec);
    }
    else
    {
        timeStr = last_login;
    }

    this->lastlogintime = timeStr;

    if ( save )
    {
        //Store in database
        if(!db->Command("UPDATE characters SET last_login='%s' WHERE id='%d'", timeStr.GetData(),
            this->GetCharacterID()))
        {
             Error2( "Last login storage: DB Error: %s\n", db->GetLastError() );
             return;
        }
    }
}

csString psCharacter::GetLastLoginTime()
{
    return this->lastlogintime;
}


bool psCharacter::LoadSpells(unsigned int use_id)
{
    // Load spells in asc since we use push to create the spell list.
    Result spells(db->Select("SELECT * from player_spells where player_id=%u order by spell_slot asc",use_id));
    if (spells.IsValid())
    {
        int i,count=spells.Count();

        for (i=0;i<count;i++)
        {
            psSpell * spell = CacheManager::GetSingleton().GetSpellByID(spells[i].GetInt("spell_id"));
            if (spell != NULL)
                AddSpell(spell);
            else
            {
                Error2("Spell id=%i not found in cachemanager", spells[i].GetInt("spell_id"));
            }
        }
        return true;
    }
    else
        return false;
}

bool psCharacter::LoadAdvantages(unsigned int use_id)
{
    // Load advantages/disadvantages
    Result adv(db->Select("SELECT * from character_advantages where character_id=%u",use_id));
    if (adv.IsValid())
    {
        unsigned int i;
        for (i=0;i<adv.Count();i++)
        {
            AddAdvantage((PSCHARACTER_ADVANTAGE)(adv[i].GetInt("advantage_id")));
        }
       return true;
    }
    else
        return false;
}

bool psCharacter::LoadSkills(unsigned int use_id)
{
    // Load skills
    Result skillResult(db->Select("SELECT * from character_skills where character_id=%u",use_id));
    
    for ( int z = 0; z < PSSKILL_COUNT; z++ )
    {
        skills.SetSkillInfo( (PSSKILL)z, CacheManager::GetSingleton().GetSkillByID((PSSKILL)z), false );        
    }

    if (skillResult.IsValid())
    {
        unsigned int i;
        for (i=0;i<skillResult.Count();i++)
        {
            if (skillResult[i]["skill_id"]!=NULL)
            {
                PSSKILL skill = (PSSKILL)skillResult[i].GetInt("skill_id");                
                skills.SetSkillPractice(  skill, skillResult[i].GetInt("skill_Z") );
                skills.SetSkillKnowledge( skill, skillResult[i].GetInt("skill_Y") );
                skills.SetSkillRank(      skill, skillResult[i].GetInt("skill_Rank"),false );
            }
        }
        skills.Calculate();
    }
    else
        return false;

    // Set stats ranks
    skills.SetSkillRank( PSSKILL_AGI , attributes.GetStat( PSITEMSTATS_STAT_AGILITY        , false), false);
    skills.SetSkillRank( PSSKILL_CHA , attributes.GetStat( PSITEMSTATS_STAT_CHARISMA       , false), false);
    skills.SetSkillRank( PSSKILL_END , attributes.GetStat( PSITEMSTATS_STAT_ENDURANCE      , false), false);
    skills.SetSkillRank( PSSKILL_INT , attributes.GetStat( PSITEMSTATS_STAT_INTELLIGENCE   , false), false);
    skills.SetSkillRank( PSSKILL_WILL ,attributes.GetStat( PSITEMSTATS_STAT_WILL           , false), false);
    skills.SetSkillRank( PSSKILL_STR,  attributes.GetStat( PSITEMSTATS_STAT_STRENGTH       , false), false);

    return true;
}

bool psCharacter::LoadTraits(unsigned int use_id)
{
    // Load traits
    Result traits(db->Select("SELECT * from character_traits where character_id=%u",use_id));
    if (traits.IsValid())
    {
        unsigned int i;
        for (i=0;i<traits.Count();i++)
        {
            psTrait *trait=CacheManager::GetSingleton().GetTraitByID(traits[i].GetInt("trait_id"));
            if (!trait)
            {
                Error3("Player ID %u has unknown trait id %s.",characterid,traits[i]["trait_id"]);
            }
            else
                SetTraitForLocation(trait->location,trait);
        }
        return true;
    }
    else
        return false;
}

void psCharacter::AddSpell(psSpell * spell)
{
    spellList.Push(spell);
}

void psCharacter::SetFullName(const char* newFirstName, const char* newLastName)
{
    if ( !newFirstName )
    {
        Error1( "Null passed as first name..." );
        return;
    }

    // Error3( "SetFullName( %s, %s ) called...", newFirstName, newLastName );
    // Update fist, last & full name
    if ( strlen(newFirstName) )
    {
        name = newFirstName;
        fullname = name;
    }
    if ( newLastName )
    {
        lastname = newLastName;
        
        if ( strlen(newLastName) )
        {
            fullname += " ";
            fullname += lastname;
        }
    }

    //Error2( "New fullname is now: %s", fullname.GetData() );
}

void psCharacter::SetRaceInfo(psRaceInfo *rinfo)
{
    raceinfo=rinfo;
    
    if ( !rinfo )
        return;
        
    attributes.SetStat(PSITEMSTATS_STAT_STRENGTH,(unsigned int)rinfo->GetBaseAttribute(PSITEMSTATS_STAT_STRENGTH), false );
    attributes.SetStat(PSITEMSTATS_STAT_AGILITY,(unsigned int)rinfo->GetBaseAttribute(PSITEMSTATS_STAT_AGILITY), false);    
    attributes.SetStat(PSITEMSTATS_STAT_ENDURANCE,(unsigned int)rinfo->GetBaseAttribute(PSITEMSTATS_STAT_ENDURANCE), false);
    attributes.SetStat(PSITEMSTATS_STAT_INTELLIGENCE,(unsigned int)rinfo->GetBaseAttribute(PSITEMSTATS_STAT_INTELLIGENCE), false);
    attributes.SetStat(PSITEMSTATS_STAT_WILL,(unsigned int)rinfo->GetBaseAttribute(PSITEMSTATS_STAT_WILL), false);
    attributes.SetStat(PSITEMSTATS_STAT_CHARISMA,(unsigned int)rinfo->GetBaseAttribute(PSITEMSTATS_STAT_CHARISMA), false);

    // Load base clothes armor or natural armor
    psItemStats *armoritem;
    if (rinfo->natural_armor_id==0)
    {
        armoritem = CacheManager::GetSingleton().GetBasicItemStatsByName("basecloths");
    }
    else
    {
        armoritem = CacheManager::GetSingleton().GetBasicItemStatsByID(rinfo->natural_armor_id);
    }

    if (armoritem==NULL) {
        Error3( "Couldn't load base armor ID: %u for char: %u!", rinfo->natural_armor_id, characterid );
        return;
    }

    inventory.GetEquipmentObject(PSCHARACTER_SLOT_ARMS).default_if_empty = armoritem->InstantiateBasicItem();
    inventory.GetEquipmentObject(PSCHARACTER_SLOT_BOOTS).default_if_empty = armoritem->InstantiateBasicItem();
    inventory.GetEquipmentObject(PSCHARACTER_SLOT_GLOVES).default_if_empty = armoritem->InstantiateBasicItem();
    inventory.GetEquipmentObject(PSCHARACTER_SLOT_HEAD).default_if_empty = armoritem->InstantiateBasicItem();
    inventory.GetEquipmentObject(PSCHARACTER_SLOT_TORSO).default_if_empty = armoritem->InstantiateBasicItem();
    inventory.GetEquipmentObject(PSCHARACTER_SLOT_LEGS).default_if_empty = armoritem->InstantiateBasicItem();

    
}

void psCharacter::SetFamiliarID( int v )
{ 
    csString sql;

    familiar_id = v; 
    sql.Format( "insert into character_relationships values ( %d, %d, 'familiar', '' )", characterid, familiar_id );
    if( !db->Command( sql ) )
    {
        Error3( "Couldn't execute SQL %s!, Character %u's pet relationship is not saved.", sql.GetData(), characterid );
    }
    
};

void psCharacter::AddAdvantage( PSCHARACTER_ADVANTAGE advantage)
{
    if (advantage<0 || advantage>=PSCHARACTER_ADVANTAGE_COUNT)
        return;
    // Get the index into the advantages array.  32 bits per entry.
    int advantage_index=(int)advantage/32;
    // Get the bit offset in the advantage entry, and use it to generate a single bit bitmask.
    unsigned int advantage_bitmask=1 << ((unsigned int)advantage % 32);

    // Set the bit
    advantage_bitfield[advantage_index]|=advantage_bitmask;
}

void psCharacter::RemoveAdvantage( PSCHARACTER_ADVANTAGE advantage)
{
    if (advantage<0 || advantage>=PSCHARACTER_ADVANTAGE_COUNT)
        return;
    // Get the index into the advantages array.  32 bits per entry.
    int advantage_index=(int)advantage/32;
    // Get the bit offset in the advantage entry, and use it to generate an inverse bitmask.
    unsigned int advantage_bitmask=~( 1 << ((unsigned int)advantage % 32));

    // Clear the bit
    advantage_bitfield[advantage_index]&=advantage_bitmask;
}


bool psCharacter::HasAdvantage ( PSCHARACTER_ADVANTAGE advantage)
{
    if (advantage<0 || advantage>=PSCHARACTER_ADVANTAGE_COUNT)
        return false;
    // Get the index into the advantages array.  32 bits per entry.
    int advantage_index=(int)advantage/32;
    // Get the bit offset in the advantage entry, and use it to generate a single bit bitmask.
    unsigned int advantage_bitmask=1 << ((unsigned int)advantage % 32);

    // This will return a value other than 0 if the bit is set.
    return advantage_bitfield[advantage_index] & advantage_bitmask ? true : false;
}


int psCharacter::GetExperiencePoints() // W
{
    return vitals->GetExp();
}

void psCharacter::SetExperiencePoints(int W)
{
    vitals->SetExp(W);    
}

/*
* Will adde W to the experience points. While the number
* of experience points are greater than needed points
* for progression points the experience points are transformed
* into  progression points.
* @return Return the number of progression points gained.
*/
int psCharacter::AddExperiencePoints(int W)
{
    int pp = 0;
    int exp = vitals->GetExp();
    int progP = vitals->GetPP();
        
    exp += W;
    bool updatedPP = false;
    
    while (exp >= 200)
    {
        exp -= 200;
        progP++;
        pp++;
        updatedPP = true;
    }

    vitals->SetExp(exp);
    if(updatedPP)
        SetProgressionPoints(progP,true);

    return pp;
}

void psCharacter::SetSpouseName( const char* name )
{
    if ( !name )
        return;

    spouseName = name;

    if ( !strcmp(name,"") )
        isMarried = false;
    else
        isMarried = true;
    
}

unsigned int psCharacter::GetProgressionPoints() // X
{
    return vitals->GetPP();
}

void psCharacter::SetProgressionPoints(unsigned int X,bool save)
{
    if (save)
    {
        Debug2(LOG_SKILLXP,GetCharacterID(),"Updating PP points to %u\n",X);
        // Update the DB
        csString sql;
        sql.Format("UPDATE characters SET progression_points = '%u' WHERE id ='%u'",X,GetCharacterID());
        if(!db->Command(sql))
        {
            Error3("Couldn't execute SQL %s!, Character %u's PP points are NOT saved",sql.GetData(),GetCharacterID());
        }
    }

    vitals->SetPP( X );    
}

void psCharacter::UseProgressionPoints(unsigned int X)
{

    SetProgressionPoints(vitals->GetPP()-X,true);
}

void psCharacter::InterruptSpellCasting()
{
    if (spellCasting != NULL)
        spellCasting->Interrupt();
    SetSpellCasting(NULL);
}

void psCharacter::SetTradeWork(psWorkGameEvent * event)
{
    workEvent = event;
/*    if (workEvent != NULL)
    {
        SetMode(PSCHARACTER_MODE_WORK,0);
    }
    else
    {
        SetMode(PSCHARACTER_MODE_PEACE,0);
    } */
}

/*
void psCharacter::InterruptTradeWork()
{
    if (workEvent)
    {        
//        SetMode(PSCHARACTER_MODE_PEACE,0);
//        workInfo->Reset();
    }        
}
*/

float psCharacter::GetPowerLevel( PSSKILL skill )
{
    float waySkillRank = (float)skills.GetSkillRank(skill);
    MathScriptVar*  waySkillVar = powerScript->GetVar("WaySkill");
    MathScriptVar*  kVar        = powerScript->GetVar("KFactor");
    MathScriptVar*  powerLevel  = powerScript->GetVar("PowerLevel");

    if(!kVar)
    {
        Warning1(LOG_SPELLS,"Couldn't find the KFactor var in powerlevel script!\n");
        return 0.0f;
    }

    if(!waySkillVar)
    {
        Warning1(LOG_SPELLS,"Couldn't find the WaySkill var in powerlevel script!\n");
        return 0.0f;
    }

    if(!powerLevel)
    {
        Warning1(LOG_SPELLS,"Couldn't find the PowerLevel var in powerlevel script!\n");
        return 0.0f;
    }

    kVar->SetValue((double)KFactor);
    waySkillVar->SetValue((double)waySkillRank);

    powerScript->Execute();
    return (float)powerLevel->GetValue();
}

int psCharacter::GetMaxAllowedRealm( PSSKILL skill )
{
    int waySkillRank = skills.GetSkillRank(skill);
    
    if (waySkillRank == 0)  // zero skill = no casting
        return 0;
    
    if (!maxRealmScript)
    {
        Error1("No \"MaxRealm\" script!");
        return 0;
    }

    MathScriptVar*  waySkillVar = maxRealmScript->GetVar("WaySkill");
    MathScriptVar*  maxRealmVar = maxRealmScript->GetVar("MaxRealm");

    if(!waySkillVar)
    {
        Warning1(LOG_SPELLS,"Couldn't find the WaySkill var in MaxRealm script!");
        return 0;
    }
    
    if(!maxRealmVar)
    {
        Warning1(LOG_SPELLS,"Couldn't find the MaxRealm var in MaxRealm script!");
        return 0;
    }

    waySkillVar->SetValue((double)waySkillRank);
    maxRealmScript->Execute();
    return (int)maxRealmVar->GetValue();
}

bool psCharacter::CheckMagicKnowledge( PSSKILL skill, int realm )
{
    Skill * skillRec = skills.GetSkill(skill);
    if (!skillRec)
        return false;
    
    if (GetMaxAllowedRealm(skill) >= realm)
        return true;

    // Special case for rank 0 people just starting.
    if (skillRec->rank==0 && !skillRec->CanTrain() && realm==1)
        return true;
    else
        return false;
}

float psCharacter::GetPowerLevel()
{
    // When casting a spell the spell casting event is stored in spellCasting.
    // This function is only legal to call when a spell is casted.
    if (spellCasting == NULL)
    {
        Error2("Character %s isn't casting a spell and GetPowerLevel is called",GetCharName());
        return 0.0;
    }

    return GetPowerLevel(spellCasting->spell->GetSkill());
}

const char * psCharacter::GetModeStr()
{
    return player_mode_to_str[player_mode];
}

void psCharacter::SetMode(PSCHARACTER_MODE newmode)
{
    if (player_mode == newmode)
        return;

    uint8_t movemode;

    switch ( newmode )
    {
        default:
        case PSCHARACTER_MODE_UNKNOWN:
        {
            Error3("Unhandled mode: %d switching to %d",player_mode,newmode);
            return;
        }
        case PSCHARACTER_MODE_PEACE:
        case PSCHARACTER_MODE_SPELL_CASTING:
        case PSCHARACTER_MODE_WORK:
        case PSCHARACTER_MODE_EXHAUSTED:
        {
            static uint8_t modeID = CacheManager::GetSingleton().GetCharModeID("normal");
            movemode = modeID;
            break;
        }
        case PSCHARACTER_MODE_COMBAT:
        {
            static uint8_t modeID = CacheManager::GetSingleton().GetCharModeID("combat");
            movemode = modeID;
            break;
        }
        case PSCHARACTER_MODE_DEAD:
        {
            static uint8_t modeID = CacheManager::GetSingleton().GetCharModeID("dead");
            movemode = modeID;
            break;
        }
        case PSCHARACTER_MODE_SIT:
        {
            static uint8_t modeID = CacheManager::GetSingleton().GetCharModeID("sit");
            movemode = modeID;
            break;
        }
    }

    actor->SetAllowedToMove(newmode != PSCHARACTER_MODE_DEAD &&
                            newmode != PSCHARACTER_MODE_SIT &&
                            newmode != PSCHARACTER_MODE_EXHAUSTED);

    actor->SetAlive(newmode != PSCHARACTER_MODE_DEAD);

    actor->SetMovementMode(movemode);

    if (newmode != PSCHARACTER_MODE_COMBAT)
        SetCombatStance(PSCHARACTER_STANCE_NONE);
    
    //cancel ongoing work
    if(player_mode == PSCHARACTER_MODE_WORK){
        if (workEvent)
            {
                workEvent->Interrupt();
                workEvent = NULL;
            }
    }
    switch( newmode )
    {
        case PSCHARACTER_MODE_PEACE:
        case PSCHARACTER_MODE_SIT:
        case PSCHARACTER_MODE_EXHAUSTED:
            SetStaminaRegenerationStill();  // start stamina regen
            break;

        case PSCHARACTER_MODE_SPELL_CASTING:
        case PSCHARACTER_MODE_COMBAT:
        case PSCHARACTER_MODE_DEAD:
            SetStaminaRegenerationNone();  // no stamina regen while busy
            break;

        case PSCHARACTER_MODE_WORK:
            
            break;  // work manager sets it's own rates
    }
    
    player_mode = newmode;
}

void psCharacter::SetCombatStance(PSCHARACTER_STANCE stance)
{
    // NPCs don't have stances yet
    if (actor->GetClientID()==0)
        return;  // Stance never changes from PSCHARACTER_STANCE_NORMAL for NPCs

    if (combat_stance == stance)
        return;

    combat_stance = stance;
    Debug3(LOG_COMBAT,GetCharacterID(),"Setting stance to %d for %s",stance,actor->GetName());

    psStanceMessage msg(actor->GetClientID(),stance);
    msg.SendMessage();
}

bool psCharacter::MoveToInventory( psItem * & itemdata )
{
    if (itemdata==NULL)
        return false;

    if ( itemdata->GetBaseStats()->IsMoney() )
    {
        SetMoney( itemdata );
        return true;
    }

    int cnum = actor->GetClientID();

    if ( inventory.PutInBulk(itemdata) )
    {
        // If the item can be put in the in the inventory play a sound
        psserver->GetCharManager()->SendOutPlaySoundMessage(cnum, itemdata->GetSound(), "pickup" );

        // Refresh the player's inventory
        psserver->GetCharManager()->SendInventory(cnum);

        if (IsNPC())
        {
            psserver->GetNPCManager()->QueueInventoryPerception(GetActor(),itemdata,true);
        }
        
        return true;
    }
    else
    {
        //psserver->SendSystemInfo(cnum, inventory.lastError);
        
        // The calling function is responsible for dropping or deleting this item.
        return false;
    }
}

void psCharacter::DropItem( psItem * & item, bool transient, float angle )
{
    if (!item) return;

    int bulkslot = inventory.FindItemInTopLevelBulk(item);
    int equipslot = inventory.FindItemInTopLevelEquipment(item);

    // If we're dropping from inventory, we should propperly remove it.
    if ( bulkslot > -1 )
        item = inventory.RemoveBulk(bulkslot);
    else if ( equipslot > -1 )
        item = inventory.RemoveEquipment(equipslot);


    if (angle < 0) // If not given an angle, drop in a random direction
        angle = psserver->rng->Get()*2*PI;

    item->SetOwningCharacter(NULL,true);

    item->SetLocationInWorld( location.loc_sector,
                              location.loc_x + sin(angle),
                              location.loc_y,
                              location.loc_z + cos(angle),
                              angle );

    psserver->GetEntityManager()->CreateItem( item, transient );

    // Play the drop item sound for this item
    psserver->GetCharManager()->SendOutPlaySoundMessage( actor->GetClientID(), item->GetSound(), "drop" );

    // If this item is a purified glyph, we need to reset it for the next character
    if ( item->GetPurifyStatus() )
        dynamic_cast<psGlyph*>(item)->UnPurify();

    // Announce drop (disabled pending a drop animation)
    //psSystemMessage newmsg(actor->GetClientID(), MSG_INFO, "%s dropped %s", name.GetData(), item->GetQuantityName().GetData() );
    //newmsg.Multicast(actor->GetMulticastClients(),0,RANGE_TO_SELECT);
}

void psCharacter::CalculateEquipmentModifiers()
{
    modifiers.Clear();

    // Loop through every holding item
    for(int i = 0; i < PSCHARACTER_SLOT_COUNT; i++)
    {
        psItem *currentitem = NULL;
        currentitem=inventory.GetEquipmentItem(i);
        if(!currentitem)
            continue;

        // Check for attr bonuses
        for(int z = 0; z < PSITEMSTATS_STAT_BONUS_INDEX_COUNT; z++)
        {
            PSITEMSTATS_STAT_BONUS_INDEX stat;
            if(z == 0)
                stat = PSITEMSTATS_STAT_BONUS_INDEX_0;
            else if( z == 1)
                stat = PSITEMSTATS_STAT_BONUS_INDEX_1;
            else if( z == 2)
                stat = PSITEMSTATS_STAT_BONUS_INDEX_2;

            float bonus = currentitem->GetWeaponAttributeBonusMax(stat);

            // Add to right var
            modifiers.AddToStat(currentitem->GetWeaponAttributeBonusType(stat), (int)bonus);
        }
    }
}

void psCharacter::AddInventoryToLoot()
{
    for (unsigned int n = 0; n < PSCHARACTER_BULK_COUNT; n++)
    {
        psItem *item = inventory.GetBulkItem(n);
        if (item)
        {
            AddLootItem(item->GetCurrentStats());
        }
    }
}

void psCharacter::AddLootItem(psItemStats *item)
{
    if(!item){
	    Error2("Attempted to add 'null' loot item to character %s, ignored.",fullname.GetDataSafe());
	} else {
        loot_pending.Push(item);
	}
}

size_t psCharacter::GetLootItems(psLootMessage& msg,int entity,int cnum)
{
    // adds inventory to loot. TEMPORARLY REMOVED. see KillNPC()
    //if ( loot_pending.Length() == 0 )
    //    AddInventoryToLoot();

    if (loot_pending.Length() )
    {
        csString loot;
        loot.Append("<loot>");

        for (size_t i=0; i<loot_pending.Length(); i++)
        {
            if (!loot_pending[i]) {
              printf("Potential ERROR: why this happens?");
              continue;
            }
            csString item;
            csString escpxml_imagename = EscpXML(loot_pending[i]->GetImageName());
            csString escpxml_name = EscpXML(loot_pending[i]->GetName());
            item.Format("<li><image icon=\"%s\" count=\"1\" /><desc text=\"%s\" /><id text=\"%u\" /></li>",
                  escpxml_imagename.GetData(),
                  escpxml_name.GetData(),
                  loot_pending[i]->GetUID());
            loot.Append(item);
        }
        loot.Append("</loot>");
        Debug3(LOG_COMBAT, GetCharacterID(), "Loot was %s for %s\n",loot.GetData(), name.GetData());
        msg.Populate(entity,loot,cnum);
    }
    return loot_pending.Length();
}

bool psCharacter::RemoveLootItem(int id)
{
    size_t x;
    for (x=0; x<loot_pending.Length(); x++)
    {
        if (!loot_pending[x]) {
          printf("Potential ERROR: why this happens?");
          loot_pending.DeleteIndex(x);
          continue;
        }

        if (loot_pending[x]->GetUID() == (uint32) id)
        {
            loot_pending.DeleteIndex(x);
            return true;
        }
    }
    return false;
}

void psCharacter::ClearLoot()
{
    loot_pending.DeleteAll();
    loot_money = 0;
}

void psCharacter::SetMoney(psMoney m)
{
    money=m;
    SaveMoney();
}

void psCharacter::SetMoney( psItem *& itemdata )
{    
    /// Check to see if the item is a money item and treat as a special case. 
    if ( itemdata->GetBaseStats()->GetFlags() & PSITEMSTATS_FLAG_TRIA )
        money.AdjustTrias( itemdata->GetStackCount() );
    
    if ( itemdata->GetBaseStats()->GetFlags() & PSITEMSTATS_FLAG_HEXA )
        money.AdjustHexas( itemdata->GetStackCount() );
    
    if ( itemdata->GetBaseStats()->GetFlags() & PSITEMSTATS_FLAG_OCTA )
        money.AdjustOctas( itemdata->GetStackCount() );
    
    if ( itemdata->GetBaseStats()->GetFlags() & PSITEMSTATS_FLAG_CIRCLE )
        money.AdjustCircles( itemdata->GetStackCount() );
        
    itemdata->ItemAboutToMove();
    CacheManager::GetSingleton().RemoveInstance(itemdata);
}
            


void psCharacter::AdjustMoney(psMoney m)
{
    money.Adjust( MONEY_TRIAS, m.GetTrias() );    
    money.Adjust( MONEY_HEXAS, m.GetHexas() );        
    money.Adjust( MONEY_OCTAS, m.GetOctas() );    
    money.Adjust( MONEY_CIRCLES, m.GetCircles() );    
    SaveMoney();
}

void psCharacter::SaveMoney()
{
    if(!loaded)
        return;

    psMoney & m = money;
    psString sql;

    sql.AppendFmt("update characters set money_circles=%u, money_trias=%u, money_hexas=%u, money_octas=%u where id=%u",
                     m.GetCircles(), m.GetTrias(), m.GetHexas(), m.GetOctas(), GetCharacterID());
    if (db->Command(sql) != 1)
    {
        Error3 ("Couldn't save character's money to database.\nCommand was "
            "<%s>.\nError returned was <%s>\n",db->GetLastQuery(),db->GetLastError());
    }
}

void psCharacter::ResetStats()
{
    vitals->ResetVitals();
}

void psCharacter::CombatDrain(int slot)
{
    float mntdrain = 1.0f;
    float phydrain = 1.0f;

    static MathScript *costScript = NULL;

    if (!costScript)
        costScript = psserver->GetMathScriptEngine()->FindScript("StaminaCombat");

    if ( costScript )
    {
        // Output
        MathScriptVar* PhyDrain   = costScript->GetVar("PhyDrain");
        MathScriptVar* MntDrain   = costScript->GetVar("MntDrain");
    
        // Input
        MathScriptVar* actor    = costScript->GetOrCreateVar("Actor");
        MathScriptVar* weapon   = costScript->GetOrCreateVar("Weapon");
    
        if(!PhyDrain || !MntDrain)
        {
            Error1("Couldn't find the PhyDrain output var in StaminaCombat script!");
            return;
        }

        // Input the data
        actor->SetObject(this);
        weapon->SetObject(inventory.GetEquipmentObject(slot).item);

        costScript->Execute();

        // Get the output
        phydrain = PhyDrain->GetValue();
        mntdrain = MntDrain->GetValue();
    }

    AdjustStamina(-phydrain, true);
    AdjustStamina(-mntdrain, false);
}

float psCharacter::GetHitPointsMax() 
{ 
    return vitals->GetVital(VITAL_HITPOINTS).max; 
}

float psCharacter::GetManaMax() 
{ 
    return vitals->GetVital(VITAL_MANA).max; 
}
    
float psCharacter::GetStaminaMax(bool pys) 
{ 
    if(pys)
        return vitals->GetVital(VITAL_PYSSTAMINA).max; 
    else
        return vitals->GetVital(VITAL_MENSTAMINA).max; 
}
    

float psCharacter::AdjustHitPoints(float adjust)
{
    return AdjustVital(VITAL_HITPOINTS, DIRTY_VITAL_HP,adjust);
}


void psCharacter::SetHitPoints(float v)
{
    SetVital(VITAL_HITPOINTS, DIRTY_VITAL_HP,v);
}    

float psCharacter::AdjustHitPointsMax(float adjust)
{
    vitals->DirtyVital(VITAL_HITPOINTS, DIRTY_VITAL_HP_MAX).max += adjust;
    return vitals->GetVital(VITAL_HITPOINTS).max;    
}
void psCharacter::SetHitPointsMax(float v)
{
    CS_ASSERT_MSG("Negative Max HP!", v > -0.01f);
    vitals->DirtyVital(VITAL_HITPOINTS, DIRTY_VITAL_HP_MAX).max = v;
}
float psCharacter::GetHP()
{    
    return vitals->GetHP();   
}

float psCharacter::GetMana()
{
    return vitals->GetMana();       
}
float psCharacter::GetStamina(bool pys)
{
    return vitals->GetStamina(pys);   
}

float psCharacter::AdjustVital( int vitalName, int dirtyFlag, float adjust )
{
    vitals->DirtyVital(vitalName, dirtyFlag).value += adjust;

    if (vitals->GetVital(vitalName).value < 0) 
        vitals->GetVital(vitalName).value = 0;

    if (vitals->GetVital(vitalName).value > vitals->GetVital(vitalName).max) 
        vitals->GetVital(vitalName).value = vitals->GetVital(vitalName).max;

    return vitals->GetVital(vitalName).value;    
}

float psCharacter::SetVital( int vitalName, int dirtyFlag, float value )
{
    vitals->DirtyVital(vitalName, dirtyFlag).value = value;

    if (vitals->GetVital(vitalName).value < 0) 
        vitals->GetVital(vitalName).value = 0;

    if (vitals->GetVital(vitalName).value > vitals->GetVital(vitalName).max) 
        vitals->GetVital(vitalName).value = vitals->GetVital(vitalName).max;

    return vitals->GetVital(vitalName).value;    
}

bool psCharacter::UpdateStatDRData(csTicks now)
{
    bool res = vitals->Update(now);
    
    // if HP dropped to zero, provoke the killing process
    if (GetHP() == 0   &&   actor != NULL   &&   actor->IsAlive())
        actor->Kill(NULL);
    return res;
}

bool psCharacter::GetStatDRData(MsgEntry* me, int flags)
{
    return vitals->ConstructDRData(me,flags);
}


float psCharacter::AdjustHitPointsRate(float adjust)
{
    vitals->DirtyVital(VITAL_HITPOINTS, DIRTY_VITAL_HP_RATE).drRate += adjust;
    return vitals->GetVital(VITAL_HITPOINTS).drRate;    
}
void psCharacter::SetHitPointsRate(float v)
{
    vitals->DirtyVital(VITAL_HITPOINTS, DIRTY_VITAL_HP).drRate = v;
}


float psCharacter::AdjustMana(float adjust)
{
    return AdjustVital(VITAL_MANA, DIRTY_VITAL_MANA,adjust);
}
void psCharacter::SetMana(float v)
{
    SetVital(VITAL_MANA, DIRTY_VITAL_MANA,v);
}

float psCharacter::AdjustManaMax(float adjust)
{
    vitals->DirtyVital(VITAL_MANA, DIRTY_VITAL_MANA_MAX).max += adjust;
    return vitals->GetVital(VITAL_MANA).max;    
}
void psCharacter::SetManaMax(float v)
{
    CS_ASSERT_MSG("Negative Max MP!", v > -0.01f);
    vitals->DirtyVital(VITAL_MANA, DIRTY_VITAL_MANA_MAX).max = v;
}

float psCharacter::AdjustManaRate(float adjust)
{
    vitals->DirtyVital(VITAL_MANA, DIRTY_VITAL_MANA_RATE).drRate += adjust;
    return vitals->GetVital(VITAL_MANA).drRate;    
}

void psCharacter::SetManaRate(float v)
{
    vitals->DirtyVital(VITAL_MANA, DIRTY_VITAL_MANA_RATE).drRate = v;
}


float psCharacter::AdjustStamina(float adjust,bool pys)
{
    if(pys)
        return AdjustVital(VITAL_PYSSTAMINA, DIRTY_VITAL_PYSSTAMINA,adjust);
    else
        return AdjustVital(VITAL_MENSTAMINA, DIRTY_VITAL_MENSTAMINA,adjust);
}

void psCharacter::SetStamina(float v,bool pys)
{
    if(pys)
        SetVital(VITAL_PYSSTAMINA, DIRTY_VITAL_PYSSTAMINA,v);
    else
        SetVital(VITAL_MENSTAMINA, DIRTY_VITAL_MENSTAMINA,v);
}

float psCharacter::AdjustStaminaMax(float adjust,bool pys)
{
    if(pys)
    {
        vitals->DirtyVital(VITAL_PYSSTAMINA, DIRTY_VITAL_PYSSTAMINA_MAX).max += adjust;
        return vitals->GetVital(VITAL_PYSSTAMINA).max;        
    }
    else
    {
        vitals->DirtyVital(VITAL_MENSTAMINA, DIRTY_VITAL_MENSTAMINA_MAX).max += adjust;
        return vitals->GetVital(VITAL_MENSTAMINA).max;        
    }
}

void psCharacter::SetStaminaRegenerationNone(bool physical,bool mental)
{
    if(physical) SetStaminaRate(0.0f,true);
    if(mental)   SetStaminaRate(0.0f,false);
}

void psCharacter::SetStaminaRegenerationWalk(bool physical,bool mental)
{
    if(physical) SetStaminaRate(GetRaceInfo()->baseRegen[PSRACEINFO_STAMINA_PHYSICAL_WALK],true);
    if(mental)   SetStaminaRate(GetRaceInfo()->baseRegen[PSRACEINFO_STAMINA_MENTAL_WALK]  ,false);
}

void psCharacter::SetStaminaRegenerationStill(bool physical,bool mental)
{
    if(physical) SetStaminaRate(GetRaceInfo()->baseRegen[PSRACEINFO_STAMINA_PHYSICAL_STILL],true);
    if(mental)   SetStaminaRate(GetRaceInfo()->baseRegen[PSRACEINFO_STAMINA_MENTAL_STILL]  ,false);
}

void psCharacter::SetStaminaRegenerationWork(int skill)
{
    //Gms don't want to lose stamina when testing
    if (actor->nevertired)
        return;

    int factor = CacheManager::GetSingleton().GetSkillByID(skill)->mental_factor;
    SetStaminaRate(-5.0*(100-factor)/100, true);
    SetStaminaRate(-5.0*factor/100, false);
}
void psCharacter::CalculateMaxStamina()
{
    if(!staminaCalc)
    {
        CPrintf(CON_ERROR,"Called CalculateMaxStamina without mathscript!!!!");
        return;
    }
    // Set all the skills vars
    staminaCalc->GetOrCreateVar("STR") ->SetValue(attributes.GetStat(PSITEMSTATS_STAT_STRENGTH));
    staminaCalc->GetOrCreateVar("END") ->SetValue(attributes.GetStat(PSITEMSTATS_STAT_ENDURANCE));
    staminaCalc->GetOrCreateVar("AGI") ->SetValue(attributes.GetStat(PSITEMSTATS_STAT_AGILITY));
    staminaCalc->GetOrCreateVar("INT") ->SetValue(attributes.GetStat(PSITEMSTATS_STAT_INTELLIGENCE));
    staminaCalc->GetOrCreateVar("WILL")->SetValue(attributes.GetStat(PSITEMSTATS_STAT_WILL));
    staminaCalc->GetOrCreateVar("CHA") ->SetValue(attributes.GetStat(PSITEMSTATS_STAT_CHARISMA));

    // Calculate
    staminaCalc->Execute();

    // Set the max values
    SetStaminaMax((float)staminaCalc->GetOrCreateVar("BasePhy")->GetValue(),true);
    SetStaminaMax((float)staminaCalc->GetOrCreateVar("BaseMen")->GetValue(),false);
}

void psCharacter::SetStaminaMax(float v,bool pys)
{
    if (v < 0.0)
        v =0.1f;
    if(pys)
        vitals->DirtyVital(VITAL_PYSSTAMINA, DIRTY_VITAL_PYSSTAMINA_MAX).max = v;
    else
        vitals->DirtyVital(VITAL_MENSTAMINA, DIRTY_VITAL_MENSTAMINA_MAX).max = v;
}

float psCharacter::AdjustStaminaRate(float adjust,bool pys)
{
    if(pys)
    {
        vitals->DirtyVital(VITAL_PYSSTAMINA, DIRTY_VITAL_PYSSTAMINA_RATE).drRate += adjust;
        return vitals->GetVital(VITAL_PYSSTAMINA).drRate;        
    }
    else
    {
        vitals->DirtyVital(VITAL_MENSTAMINA, DIRTY_VITAL_MENSTAMINA_RATE).drRate += adjust;
        return vitals->GetVital(VITAL_MENSTAMINA).drRate;        
    }
    
}

void psCharacter::SetStaminaRate(float v,bool pys)
{
    if(pys)
        vitals->DirtyVital(VITAL_PYSSTAMINA, DIRTY_VITAL_PYSSTAMINA_RATE).drRate = v;
    else
        vitals->DirtyVital(VITAL_MENSTAMINA, DIRTY_VITAL_MENSTAMINA_RATE).drRate = v;
}






void psCharacter::NotifyAttackPerformed(int slot,csTicks timeofattack)
{
    psItem *Weapon;

    // Slot out of range
    if (slot<0 || slot>=PSCHARACTER_SLOT_COUNT)
        return;

    // TODO: Reduce ammo if this is an ammunition using weapon

    // Reset next attack time
    Weapon=Inventory().GetEffectiveWeaponInSlot(slot);

    if (Weapon!=NULL)
        inventory.GetEquipmentObject(slot).NextSwingTime=csTicks(timeofattack+ (Weapon->GetLatency() * 1000.0f));

    //drain stamina on player attacks
    if(actor->GetClientID() && !actor->nevertired)
        CombatDrain(slot);
}

csTicks psCharacter::GetSlotNextAttackTime(int slot)
{
    // Slot out of range
    if (slot<0 || slot>=PSCHARACTER_SLOT_COUNT)
        return (csTicks)0;

    return inventory.GetEquipmentObject(slot).NextSwingTime;
}


// AVPRO= attack Value progression (this is to change the progression of all the calculation in AV for now it is equal to 1)
#define AVPRO 1

float psCharacter::GetAttackValueModifier()
{
    return attackValueModifier;
}

void  psCharacter::AdjustAttackValueModifier(float mul)
{
    attackValueModifier *= mul;
}

float psCharacter::GetDefenseValueModifier()
{
    return defenseValueModifier;
}

void  psCharacter::AdjustDefenseValueModifier(float mul)
{
    defenseValueModifier *= mul;
}

float psCharacter::GetMeleeDefensiveDamageModifier()
{
    return meleeDefensiveDamageModifier;
}

void  psCharacter::AdjustMeleeDefensiveDamageModifier(float mul)
{
    meleeDefensiveDamageModifier *= mul;
}

float psCharacter::GetTargetedBlockValueForWeaponInSlot(int slot)
{
    psItem *weapon=Inventory().GetEffectiveWeaponInSlot(slot);
    if (weapon==NULL)
        return 0.0f;

    return weapon->GetTargetedBlockValue();
}

float psCharacter::GetUntargetedBlockValueForWeaponInSlot(int slot)
{
    psItem *weapon=Inventory().GetEffectiveWeaponInSlot(slot);
    if (weapon==NULL)
        return 0.0f;

    return weapon->GetUntargetedBlockValue();
}


float psCharacter::GetTotalTargetedBlockValue()
{
    float blockval=0.0f;
    int slot;

    for (slot=0;slot<PSCHARACTER_SLOT_COUNT;slot++)
        blockval+=GetTargetedBlockValueForWeaponInSlot(slot);

    return blockval;
}

float psCharacter::GetTotalUntargetedBlockValue()
{
    float blockval=0.0f;
    int slot;

    for (slot=0;slot<PSCHARACTER_SLOT_COUNT;slot++)
        blockval+=GetUntargetedBlockValueForWeaponInSlot(slot);

    return blockval;
}


float psCharacter::GetCounterBlockValueForWeaponInSlot(int slot)
{
    psItem *weapon=inventory.GetEffectiveWeaponInSlot(slot);
    if (weapon==NULL)
        return 0.0f;

    return weapon->GetCounterBlockValue();
}

#define ARMOR_USES_SKILL(slot,skill) inventory.GetEquipmentItem(slot)==NULL?inventory.GetEquipmentObject(slot).default_if_empty->GetArmorType()==skill:inventory.GetEquipmentItem(slot)->GetArmorType()==skill

#define CALCULATE_ARMOR_FOR_SLOT(slot) \
    if (ARMOR_USES_SKILL(slot,PSITEMSTATS_ARMORTYPE_LIGHT)) light_p+=1.0f/6.0f; \
    if (ARMOR_USES_SKILL(slot,PSITEMSTATS_ARMORTYPE_MEDIUM)) med_p+=1.0f/6.0f; \
    if (ARMOR_USES_SKILL(slot,PSITEMSTATS_ARMORTYPE_HEAVY)) heavy_p+=1.0f/6.0f;


float psCharacter::GetDodgeValue()
{
    float heavy_p,med_p,light_p;
    float asdm;

    // hold the % of each type of armor worn
    heavy_p=med_p=light_p=0.0f;

    CALCULATE_ARMOR_FOR_SLOT(PSCHARACTER_SLOT_HEAD);
    CALCULATE_ARMOR_FOR_SLOT(PSCHARACTER_SLOT_TORSO);
    CALCULATE_ARMOR_FOR_SLOT(PSCHARACTER_SLOT_ARMS);
    CALCULATE_ARMOR_FOR_SLOT(PSCHARACTER_SLOT_GLOVES);
    CALCULATE_ARMOR_FOR_SLOT(PSCHARACTER_SLOT_LEGS);
    CALCULATE_ARMOR_FOR_SLOT(PSCHARACTER_SLOT_BOOTS);

    // multiplies for skill
    heavy_p *= skills.GetSkillRank(PSSKILL_HEAVYARMOR);
    med_p   *= skills.GetSkillRank(PSSKILL_MEDIUMARMOR);
    light_p *= skills.GetSkillRank(PSSKILL_LIGHTARMOR);

    // armor skill defense mod
    asdm=heavy_p+med_p+light_p;

    // if total skill is 0, give a little chance anyway to defend himself
    if (asdm==0)
        asdm=0.2F;

    // for now return just asdm
    return asdm;

    /*
    // MADM= Martial Arts Defense Mod=martial arts skill-(weight carried+ AGI malus of the armor +DEX malus of the armor) min 0
    // TODO: fix this to use armor agi malus
    madm=GetSkillRank(PSSKILL_MARTIALARTS)-inventory.weight;
    if (madm<0.0f)
        madm=0.0f;

    // Active Dodge Value =ADV = (ASDM + agiDmod + MADM)*ADVP)^0.4
    // Out of Melee Defense Value=OMDV = (agiDmod+(ASDM\10)*PDVP)^0.4

    // TODO:  Use passive dv here when the conditions are finalized that it should be used (archery, casting, more?)

    dv=asdm + AgiMod  + madm;
    dv=pow(dv,(float)0.6);

    return dv;
    */
}


void psCharacter::PracticeArmorSkills(unsigned int practice, int attackLocation)
{

    psItem *armor = inventory.GetEffectiveArmorInSlot(attackLocation);
    
    switch (armor->GetArmorType())
    {
        case PSITEMSTATS_ARMORTYPE_LIGHT:
            skills.AddSkillPractice(PSSKILL_LIGHTARMOR,practice);
            break;
        case PSITEMSTATS_ARMORTYPE_MEDIUM:
            skills.AddSkillPractice(PSSKILL_MEDIUMARMOR,practice);
            break;
        case PSITEMSTATS_ARMORTYPE_HEAVY:
            skills.AddSkillPractice(PSSKILL_HEAVYARMOR,practice);
            break;
        default:
            break;
    }
    
}

void psCharacter::PracticeWeaponSkills(unsigned int practice)
{
    int slot;

    for (slot=0;slot<PSCHARACTER_SLOT_COUNT;slot++)
    {
        psItem *weapon=inventory.GetEffectiveWeaponInSlot(slot);
        if (weapon!=NULL)
            PracticeWeaponSkills(weapon,practice);
    }
    
}

void psCharacter::PracticeWeaponSkills(psItem * weapon, unsigned int practice)
{
    for (int index = 0; index < PSITEMSTATS_WEAPONSKILL_INDEX_COUNT; index++)
    {
        PSSKILL skill = weapon->GetWeaponSkill((PSITEMSTATS_WEAPONSKILL_INDEX)index);        
        if (skill != PSSKILL_NONE)
            skills.AddSkillPractice(skill,practice);
    }
}

void psCharacter::SetTraitForLocation(PSTRAIT_LOCATION location,psTrait *trait)
{
    if (location<0 || location>=PSTRAIT_LOCATION_COUNT)
        return;

    traits[location]=trait;
}

psTrait *psCharacter::GetTraitForLocation(PSTRAIT_LOCATION location)
{
    if (location<0 || location>=PSTRAIT_LOCATION_COUNT)
        return NULL;

    return traits[location];
}


void psCharacter::GetLocationInWorld(psSectorInfo *&sectorinfo,float &loc_x,float &loc_y,float &loc_z,float &loc_yrot)
{
    sectorinfo=location.loc_sector;
    loc_x=location.loc_x;
    loc_y=location.loc_y;
    loc_z=location.loc_z;
    loc_yrot=location.loc_yrot;
}

void psCharacter::SetLocationInWorld(psSectorInfo *sectorinfo,float loc_x,float loc_y,float loc_z,float loc_yrot)
{
    psSectorInfo *oldsector = location.loc_sector;
    
    location.loc_sector=sectorinfo;
    location.loc_x=loc_x;
    location.loc_y=loc_y;
    location.loc_z=loc_z;
    location.loc_yrot=loc_yrot;
    
    if (oldsector!=NULL  &&  oldsector!=sectorinfo)
    {
        if ( dynamic_cast<gemNPC*>(actor) == NULL ) // NOT an NPC so it's ok to save location info
            SaveLocationInWorld();
    }
}

void psCharacter::SaveLocationInWorld()
{
    if(!loaded)
        return;

    st_location & l = location;
    psString sql;

    sql.AppendFmt("update characters set loc_x=%10.2f, loc_y=%10.2f, loc_z=%10.2f, loc_yrot=%10.2f, loc_sector_id=%u where id=%u",
                     l.loc_x, l.loc_y, l.loc_z, l.loc_yrot, l.loc_sector->uid, characterid);
    if (db->Command(sql) != 1)
    {
        Error3 ("Couldn't save character's position to database.\nCommand was "
            "<%s>.\nError returned was <%s>\n",db->GetLastQuery(),db->GetLastError());
    }
}




psSpell * psCharacter::GetSpellByName(const csString& spellName)
{
    for (size_t i=0; i < spellList.Length(); i++)
    {
        if (spellList[i]->GetName() == spellName) return spellList[i];
    }
    return NULL;
}

psSpell * psCharacter::GetSpellByIdx(int index)
{
    if (index < 0 || (size_t)index >= spellList.Length())
        return NULL;
    return spellList[index];
}

csString psCharacter::GetXMLSpellList()
{
    csString buff = "<L>";
    for (size_t i=0; i < spellList.Length(); i++)
    {
        buff.Append(spellList[i]->SpellToXML());
    }
    buff.Append("</L>");
    return buff;
}

bool psCharacter::SetTradingStopped(bool stopped)
{
    bool old = tradingStopped;
    tradingStopped=stopped;
    return old;
}

// Check if player and target is ready to do a exchange
//       - Not fighting
//       - Not casting spell
//       - Not exchanging with a third player
//       - Not player stopped trading
//       - Not trading with a merchant
bool psCharacter::ReadyToExchange()
{
    return (//TODO: Test for fighting &&
        //TODO: Test for casting spell
       //  !exchangeMgr.IsValid() &&
        !tradingStopped &&
        tradingStatus == NOT_TRADING);

}

csArray<psItem*> psCharacterInventory::GetItemsInCategory(psItemCategory * category)
{
    csArray<psItem*> items;

    for (int i = 0; i < PSCHARACTER_BULK_COUNT; i ++)
    {
        if (bulk[i])
        {
            if (bulk[i]->GetCategory() == category)
            {
                items.Push(bulk[i]);
            }
        }

    }

    return items;
}

void psCharacter::MakeTextureString( csString& traits)
{
    // initialize string
    traits = "<traits>";

    // cycle through and add entries for each part
    for (unsigned int i=0;i<PSTRAIT_LOCATION_COUNT;i++)
    {
        psTrait *trait;
        trait = GetTraitForLocation((PSTRAIT_LOCATION)i);
        while (trait != NULL)
        {
            csString buff = trait->ToXML(true);
            traits.Append(buff);
            trait = trait->next_trait;
        }
    }
    
    // terminate string
    traits.Append("</traits>");

    Notify2( LOG_CHARACTER, "Traits string: %s", (const char*)traits );
}

void psCharacter::MakeEquipmentString( csString& equipment )
{
    equipment = "<equiplist>";
    equipment.AppendFmt("<helm>%s</helm>", EscpXML(helmGroup).GetData());
    
    for (int i=0; i<PSCHARACTER_SLOT_COUNT; i++)
    {
        psItem* item = inventory.GetEquipmentItem(i);
        if (item == NULL) continue;       
        csString slot = EscpXML( CacheManager::GetSingleton().slotName.GetName(i) );
        csString mesh = EscpXML( item->GetMeshName() );
        csString part = EscpXML( item->GetPartName() );
        csString texture = EscpXML( item->GetTextureName() );
        csString partMesh = EscpXML( item->GetPartMeshName() );

        //if (part.Length() && texture.Length()) // Worn
        //{
        //    equipment.AppendFmt("<equip part=\"%s\" texture=\"%s\" />", part.GetData(), texture.GetData() );
        //}
        //else if (slot.Length() && mesh.Length()) // Weilded
        //{
            equipment.AppendFmt("<equip slot=\"%s\" mesh=\"%s\" part=\"%s\" texture=\"%s\" partMesh=\"%s\"  />", 
                                slot.GetData(), mesh.GetData(), part.GetData(), texture.GetData(), partMesh.GetData() );
        //}
    }

    equipment.Append("</equiplist>");

    Notify2( LOG_CHARACTER, "Equipment string: %s", equipment.GetData() );
}


bool psCharacter::AppendCharacterSelectData(psAuthApprovedMessage& auth)
{
    csString traits;
    csString equipment;

    MakeTextureString( traits );
    MakeEquipmentString( equipment );

    auth.AddCharacter(fullname, raceinfo->name, raceinfo->mesh_name, traits, equipment);
    return true;
}

QuestAssignment *psCharacter::IsQuestAssigned(int id)
{
    for (size_t i=0; i<assigned_quests.Length(); i++)
    {
        if (assigned_quests[i]->quest->GetID() == id &&
            assigned_quests[i]->status != PSQUEST_DELETE)
            return assigned_quests[i];
    }

    return NULL;
}

size_t  psCharacter::GetAssignedQuests(psQuestListMessage& questmsg,int cnum)
{
    if (assigned_quests.Length() )
    {
        csString quests;
        quests.Append("<quests>");
        csArray<QuestAssignment*>::Iterator iter = assigned_quests.GetIterator();

        while(iter.HasNext())
        {
            QuestAssignment* assigned_quest = iter.Next();
            // exclude deleted
            if (assigned_quest->status == PSQUEST_DELETE)
                continue;
            // exclude substeps
            if (assigned_quest->quest->GetParentQuest())
                continue;

            csString item;
            csString escpxml_image = EscpXML(assigned_quest->quest->GetImage());
            csString escpxml_name = EscpXML(assigned_quest->quest->GetName());
            item.Format("<q><image icon=\"%s\" /><desc text=\"%s\" /><id text=\"%d\" /><status text=\"%c\" /></q>",
                        escpxml_image.GetData(),
                        escpxml_name.GetData(),
                        assigned_quest->quest->GetID(),
                        assigned_quest->status );
            quests.Append(item);
        }
        quests.Append("</quests>");
        Debug2(LOG_QUESTS, GetCharacterID(), "QuestMsg was %s\n",quests.GetData() );
        questmsg.Populate(quests,cnum);
    }
    return assigned_quests.Length();
}

QuestAssignment *psCharacter::AssignQuest(psQuest *quest, int assigner_id)
{
    CS_ASSERT( quest );  // Must not be NULL

    QuestAssignment *q = IsQuestAssigned(quest->GetID() );
    if (!q)  // make new entry if needed, reuse if old
    {
        q = new QuestAssignment;
        q->quest = quest;
        q->status = PSQUEST_DELETE;

        assigned_quests.Push(q);
    }

    if (q->status != PSQUEST_ASSIGNED)
    {
        q->dirty  = true;
        q->status = PSQUEST_ASSIGNED;
        q->lockout_end = 0;
        q->assigner_id = assigner_id;

        // assign any skipped parent quests
        if (quest->GetParentQuest() && !IsQuestAssigned(quest->GetParentQuest()->GetID()))
                AssignQuest(quest->GetParentQuest(),assigner_id );

        // assign any skipped sub quests
        csHash<psQuest*>::GlobalIterator it = CacheManager::GetSingleton().GetQuestIterator();
        while (it.HasNext())
        {
            psQuest * q = it.Next();
            if (q->GetParentQuest())
            {
                if (q->GetParentQuest()->GetID() == quest->GetID())
                    AssignQuest(q,assigner_id);
            }
        }

        q->quest->SetQuestLastActivatedTime( csGetTicks() );

        Debug3(LOG_QUESTS, GetCharacterID(), "Assigned quest '%s' to player '%s'\n",quest->GetName(),GetCharName() );
        UpdateQuestAssignments();
    }
    else
    {
        Debug3(LOG_QUESTS, GetCharacterID(), "Did not assign %s quest to %s because it was already assigned.\n",quest->GetName(),GetCharName() );
    }

    return q;
}

bool psCharacter::CompleteQuest(psQuest *quest)
{
    CS_ASSERT( quest );  // Must not be NULL

    QuestAssignment *q = IsQuestAssigned( quest->GetID() );
    QuestAssignment *parent = NULL;

    // substeps are not assigned, so the above check fails for substeps.
    // in this case we check if the parent quest is assigned
    if (!q && quest->GetParentQuest()) {
      parent = IsQuestAssigned(quest->GetParentQuest()->GetID() );
    }

    // create an assignment for the substep if parent is valid
    if (parent) {
      q = AssignQuest(quest,parent->assigner_id);
    }

    if (q)
    {
        if (q->status == PSQUEST_DELETE || q->status == PSQUEST_COMPLETE)
        {
            Debug3(LOG_QUESTS, GetCharacterID(), "Player '%s' has already completed quest '%s'.  No credit.\n",GetCharName(),quest->GetName() );
            return false;  // already completed, so no credit here
        }

        q->dirty  = true;
        q->status = PSQUEST_COMPLETE; // completed
        q->lockout_end = csGetTicks() + q->quest->GetPlayerLockoutTime();

        // Complete all substeps if this is the parent quest
        if (!q->quest->GetParentQuest())
        {
            // assign any skipped sub quests
            csHash<psQuest*>::GlobalIterator it = CacheManager::GetSingleton().GetQuestIterator();
            while (it.HasNext())
            {
                psQuest * q = it.Next();
                if (q->GetParentQuest())
                {
                    if (q->GetParentQuest()->GetID() == quest->GetID())
                        CompleteQuest(q);
                }
            }
        }

        Debug3(LOG_QUESTS, GetCharacterID(), "Player '%s' just completed quest '%s'.\n",GetCharName(),quest->GetName() );
        UpdateQuestAssignments();
        return true;
    }

    return false;
}

void psCharacter::DiscardQuest(QuestAssignment *q)
{
    CS_ASSERT( q );  // Must not be NULL

    if (q->status != PSQUEST_DELETE)
    {
        q->dirty = true;
        q->status = PSQUEST_DELETE;  // discarded
        q->lockout_end = csGetTicks() + q->quest->GetPlayerLockoutTime();  // assignment entry will be deleted after expiration

        Debug3(LOG_QUESTS, GetCharacterID(), "Player '%s' just discarded quest '%s'.\n",GetCharName(),q->quest->GetName() );
        UpdateQuestAssignments();
    }
    else
    {
        Debug3(LOG_QUESTS, GetCharacterID(), "Did not discard %s quest for player %s because it was already discarded.\n",q->quest->GetName(),GetCharName() );
    }
}

bool psCharacter::CheckQuestAssigned(psQuest *quest)
{
    CS_ASSERT( quest );  // Must not be NULL

    QuestAssignment* questAssignment = IsQuestAssigned( quest->GetID() );
    if ( questAssignment )
    {
        if ( questAssignment->status == PSQUEST_ASSIGNED)
            return true;
    }
    else
        Warning4( LOG_QUESTS, "No QuestAssignment for quest '%s'(%d) for player '%s'.\n", 
                  quest->GetName(),quest->GetID(), GetCharName() );

    return false;
}

bool psCharacter::CheckQuestCompleted(psQuest *quest)
{
    CS_ASSERT( quest );  // Must not be NULL

    QuestAssignment* questAssignment = IsQuestAssigned( quest->GetID() );
    if ( questAssignment )
    {
        if ( questAssignment->status == PSQUEST_COMPLETE)
            return true;
    }
    else
        Warning4( LOG_QUESTS, "QuestAssignment for quest '%s'(%d) not found for player '%s'.\n", 
                  quest->GetName(),quest->GetID(), GetCharName() );

    return false;
}

bool psCharacter::CheckQuestAvailable(psQuest *quest,int assigner_id)
{
    CS_ASSERT( quest );  // Must not be NULL

    csTicks now = csGetTicks();
    bool quest_started = false;

    if (quest->GetParentQuest())
        quest = quest->GetParentQuest();

    bool notify = false;
    if (GetActor()->GetClient())
    {
        notify = CacheManager::GetSingleton().GetCommandManager()->Validate(GetActor()->GetClient()->GetSecurityLevel(), "quest notify");
    }

    for (size_t i=0; i<assigned_quests.Length(); i++)
    {
        if (assigned_quests[i]->assigner_id == assigner_id &&
            assigned_quests[i]->quest->GetID() != quest->GetID() &&
            assigned_quests[i]->quest->GetParentQuest() == NULL &&
            assigned_quests[i]->status == PSQUEST_ASSIGNED)
        {
            if (notify)
            {
                psserver->SendSystemInfo(GetActor()->GetClientID(),
                                         "GM NOTICE: Quest found, but you already have one assigned from same NPC");
            }

            return false; // Cannot have multiple quests from the same guy
        }
        // Character have this quest
        if (assigned_quests[i]->quest->GetID() == quest->GetID())
        {
            // Still in lockout
            if (assigned_quests[i]->lockout_end > now)
            {
                if (notify)
                {
                    if (GetActor()->questtester) // GM flag
                    {
                        psserver->SendSystemInfo(GetActor()->GetClientID(),
                                                 "GM NOTICE: Quest (%s) found and player lockout time has been overridden.",
                                                 quest->GetName());
                        return true; // Quest is available for GM
                    }
                    else
                        psserver->SendSystemInfo(GetActor()->GetClientID(),
                                                 "GM NOTICE: Quest (%s) found but player lockout time hasn't elapsed yet. %d seconds remaining.",
                                                 quest->GetName(), (assigned_quests[i]->lockout_end - now)/1000 );
                }

                return false; // Cannot have the same quest while in player lockout time.
            }

            if (assigned_quests[i]->status == PSQUEST_ASSIGNED)
            {
                quest_started = true;
            }
        }
    }

    // If player dosn't have this quest check if quest have lockout
    if (!quest_started && quest->GetQuestLastActivatedTime() && 
        quest->GetQuestLastActivatedTime() + quest->GetQuestLockoutTime() > now)
    {
        if (notify)
        {
            if (GetActor()->questtester) // GM flag
            {
                psserver->SendSystemInfo(GetActor()->GetClientID(),
                                         "GM NOTICE: Quest(%s) found; quest lockout time has been overrided",
                                         quest->GetName());
                return true; // Quest is available for GM
            }
            else
                psserver->SendSystemInfo(GetActor()->GetClientID(),
                                         "GM NOTICE: Quest(%s) found, but quest lockout time hasn't elapsed yet",
                                         quest->GetName());
        }

        return false; // Cannot start this quest while in quest lockout time.
    }
    
    return true; // Quest is available
}

bool psCharacter::CheckResponsePrerequisite(NpcResponse *resp)
{
    CS_ASSERT( resp );  // Must not be NULL

    return resp->CheckPrerequisite(this);
}

int psCharacter::NumberOfQuestsCompleted(csString category)
{
    int count=0;
    for (size_t i=0; i<assigned_quests.Length(); i++)
    {
        // Character have this quest
        if (assigned_quests[i]->quest->GetParentQuest() == NULL &&
            assigned_quests[i]->status == PSQUEST_COMPLETE &&
            assigned_quests[i]->quest->GetCategory() == category)
        {
            count++;
        }
    }
    return count;
}

bool psCharacter::UpdateQuestAssignments(bool force_update)
{
    csTicks now = csGetTicks();

    for (size_t i=0; i<assigned_quests.Length(); i++)
    {
        QuestAssignment *q = assigned_quests[i];
        if (q->dirty || force_update)
        {
            int r;

            // will delete the quest only after the expiration time, so the player cannot get it again immediately
            if (q->status == PSQUEST_DELETE &&
                (!q->quest->GetPlayerLockoutTime() || !q->lockout_end ||
                 (q->lockout_end < now))) // delete
            {
                r = db->Command("delete from character_quests"
                                " where player_id=%d"
                                "   and quest_id=%d",
                                characterid,q->quest->GetID() );

                delete assigned_quests[i];
                assigned_quests.DeleteIndex(i);
                i--;  // reincremented in loop
                continue;
            }

            // Update or create a new entry in DB

            // Store remaining time to DB. Than remaining time has to be added to current time
            // at load from DB.
            csTicks remaining_time = 0;
            if (q->lockout_end && q->lockout_end > now)
                remaining_time = q->lockout_end - now;

            r = db->Command("update character_quests "
                            "set status='%c',"
                            "remaininglockout=%ld "
                            " where player_id=%d"
                            "   and quest_id=%d",
                            q->status,
                            remaining_time,
                            characterid,
                            q->quest->GetID() );
            if (!r)  // no update done
            {
                r = db->Command("insert into character_quests"
                                "(player_id, assigner_id, quest_id, status, remaininglockout) "
                                "values (%d, %d, %d, '%c', %d)",
                                characterid,
                                q->assigner_id,
                                q->quest->GetID(),
                                q->status,
                                remaining_time);

                if (r == -1)
                {
                    Error2("Could not insert character_quest row.  Error was <%s>.",db->GetLastError() );
                }
                else
                {
                    Debug3(LOG_QUESTS, GetCharacterID(), "Inserted quest info for player %d, quest %d.\n",characterid,assigned_quests[i]->quest->GetID() );
                }
            }
            else
            {
                Debug3(LOG_QUESTS, GetCharacterID(), "Updated quest info for player %d, quest %d.\n",characterid,assigned_quests[i]->quest->GetID() );
            }
            assigned_quests[i]->dirty = false;
        }
    }
    return true;
}


bool psCharacter::LoadQuestAssignments()
{
    Result result(db->Select("select * from character_quests"
                             " where player_id=%d", characterid));
    if (!result.IsValid())
    {
        Error3("Could not load quest assignments for character %d. Error was: %s",characterid, db->GetLastError() );
        return false;
    }

    csTicks now = csGetTicks();

    for (unsigned int i=0; i<result.Count(); i++)
    {
        QuestAssignment *q = new QuestAssignment;
        q->dirty = false;
        q->quest = CacheManager::GetSingleton().GetQuestByID( result[i].GetInt("quest_id") );
        q->status = result[i]["status"][0];
        q->lockout_end = now + result[i].GetInt("remaininglockout");
        q->assigner_id = result[i].GetInt("assigner_id");

        if (!q->quest)
        {
            Error3("Quest %d for player %d not found!", result[i].GetInt("quest_id"), characterid );
            delete q;
            return false;
        }

        // Sanity check to see if time for completion is withing 
        // lockout time.
        if (q->lockout_end > now + q->quest->GetPlayerLockoutTime())
            q->lockout_end = now + q->quest->GetPlayerLockoutTime();

        Debug5(LOG_QUESTS, characterid, "Loaded quest %-40.40s, status %c, lockout %u, for player %s.\n",
               q->quest->GetName(),q->status,
               ( q->lockout_end > now ? q->lockout_end-now:0),GetCharFullName());
        assigned_quests.Push(q);
    }
    return true;
}


psGuildLevel * psCharacter::GetGuildLevel()
{
    if (guildinfo == NULL)
        return 0;

    psGuildMember * membership = guildinfo->FindMember((unsigned int)characterid);
    if (membership == NULL)
        return 0;

    return membership->guildlevel;
}

void psCharacter::RemoveBuddy( unsigned int buddyID )
{
    for ( size_t x = 0; x < buddyList.Length(); x++ )
    {
        if ( buddyList[x].playerID == buddyID )
        {
            buddyList.DeleteIndex(x);
            return;
        }
    }    
}

void psCharacter::BuddyOf( unsigned int buddyID )
{
    if ( buddyOfList.Find( buddyID )  == csArrayItemNotFound )
    {
        buddyOfList.Push( buddyID );
    }
}

void psCharacter::NotBuddyOf( unsigned int buddyID )
{
    buddyOfList.Delete( buddyID );
}

bool psCharacter::AddBuddy( unsigned int buddyID, csString& buddyName )
{
    // Cannot addself to buddy list
    if ( buddyID == characterid )
        return false;
        
    for ( size_t x = 0; x < buddyList.Length(); x++ )
    {
        if ( buddyList[x].playerID == buddyID )
        {
            return true;
        }
    }    
    
    Buddy b;
    b.name = buddyName;
    b.playerID = buddyID;
    
    buddyList.Push( b );  

    return true;
}



double psCharacter::GetProperty(const char *ptr)
{
    if (!strcasecmp(ptr,"AttackerTargeted"))
    {
        return true;
        // return (attacker_targeted) ? 1 : 0;
    }
    else if (!strcasecmp(ptr,"TotalTargetedBlockValue"))
    {
        return GetTotalTargetedBlockValue();
    }
    else if (!strcasecmp(ptr,"TotalUntargetedBlockValue"))
    {
        return GetTotalUntargetedBlockValue();
    }
    else if (!strcasecmp(ptr,"DodgeValue"))
    {
        return GetDodgeValue();
    }
    else if (!strcasecmp(ptr,"KillExp"))
    {
        return kill_exp;
    }
    else if (!strcasecmp(ptr,"getAttackValueModifier"))
    {
        return attackValueModifier;
    }
    else if (!strcasecmp(ptr,"getDefenseValueModifier"))
    {
        return defenseValueModifier;
    }
    else if (!strcasecmp(ptr,"Strength"))
    {
        return attributes.GetStat(PSITEMSTATS_STAT_STRENGTH,true);
    }
    else if (!strcasecmp(ptr,"Agility"))
    {
        return attributes.GetStat(PSITEMSTATS_STAT_AGILITY,true);
    }
    else if (!strcasecmp(ptr,"Endurance"))
    {
        return attributes.GetStat(PSITEMSTATS_STAT_ENDURANCE,true);
    }
    else if (!strcasecmp(ptr,"Intelligence"))
    {
        return attributes.GetStat(PSITEMSTATS_STAT_INTELLIGENCE,true);
    }
    else if (!strcasecmp(ptr,"Will"))
    {
        return attributes.GetStat(PSITEMSTATS_STAT_WILL,true);
    }
    else if (!strcasecmp(ptr,"Charisma"))
    {
        return attributes.GetStat(PSITEMSTATS_STAT_CHARISMA,true);
    }
    else if (!strcasecmp(ptr,"BaseStrength"))
    {
        return attributes.GetStat(PSITEMSTATS_STAT_STRENGTH,false);
    }
    else if (!strcasecmp(ptr,"BaseAgility"))
    {
        return attributes.GetStat(PSITEMSTATS_STAT_AGILITY,false);
    }
    else if (!strcasecmp(ptr,"BaseEndurance"))
    {
        return attributes.GetStat(PSITEMSTATS_STAT_ENDURANCE,false);
    }
    else if (!strcasecmp(ptr,"BaseIntelligence"))
    {
        return attributes.GetStat(PSITEMSTATS_STAT_INTELLIGENCE,false);
    }
    else if (!strcasecmp(ptr,"BaseWill"))
    {
        return attributes.GetStat(PSITEMSTATS_STAT_WILL,false);
    }
    else if (!strcasecmp(ptr,"BaseCharisma"))
    {
        return attributes.GetStat(PSITEMSTATS_STAT_CHARISMA,false);
    }
    else if (!strcasecmp(ptr,"AllArmorStrMalus"))
    {
        return modifiers.GetStat(PSITEMSTATS_STAT_STRENGTH);
    }
    else if (!strcasecmp(ptr,"AllArmorAgiMalus"))
    {
        return modifiers.GetStat(PSITEMSTATS_STAT_AGILITY);
    }
    else if (!strcasecmp(ptr,"CombatStance"))
    {
        return GetCombatStance();
    }
    Error2("Requested psCharacter property not found '%s'", ptr);
    return 0;
}

/** A skill can only be trained if the player requires points for it.
  */
bool psCharacter::CanTrain( PSSKILL skill )
{
    return skills.CanTrain( skill );
}

void psCharacter::Train( PSSKILL skill, int yIncrease )
{
    // Did we train stats?
    if( skill == PSSKILL_AGI ||
        skill == PSSKILL_CHA ||
        skill == PSSKILL_END ||
        skill == PSSKILL_INT ||
        skill == PSSKILL_WILL ||
        skill == PSSKILL_STR )
    {
        Skill* cskill = skills.GetSkill(skill);

        skills.Train( skill, yIncrease );
        int know = cskill->y;
        int cost = cskill->yCost;

        // We ranked up
        if(know >= cost)
        {
            cskill->rank++;
            cskill->y = 0;
            int after = cskill->rank;
            cskill->CalculateCosts(this);

            // Insert into the stats
            switch(skill)
            {
                case PSSKILL_AGI:
                    attributes.SetStat(PSITEMSTATS_STAT_AGILITY,after); break;
                case PSSKILL_CHA:
                    attributes.SetStat(PSITEMSTATS_STAT_CHARISMA,after); break;
                case PSSKILL_END:
                    attributes.SetStat(PSITEMSTATS_STAT_ENDURANCE,after); break;
                case PSSKILL_INT:
                    attributes.SetStat(PSITEMSTATS_STAT_INTELLIGENCE,after); break;
                case PSSKILL_WILL:
                    attributes.SetStat(PSITEMSTATS_STAT_WILL,after); break;
                case PSSKILL_STR:
                    attributes.SetStat(PSITEMSTATS_STAT_STRENGTH,after); break;
                default:
                    break;
            }

            // Save stats
            if(GetActor()->GetClientID() != 0)
            {
                const char *fieldnames[]= {
                   "base_strength",
                   "base_agility",
                   "base_endurance",
                   "base_intelligence",
                   "base_will",
                   "base_charisma"
                    };
                psStringArray fieldvalues;
                fieldvalues.FormatPush("%d",attributes.GetStat(PSITEMSTATS_STAT_STRENGTH, false));
                fieldvalues.FormatPush("%d",attributes.GetStat(PSITEMSTATS_STAT_AGILITY, false));
                fieldvalues.FormatPush("%d",attributes.GetStat(PSITEMSTATS_STAT_ENDURANCE, false));
                fieldvalues.FormatPush("%d",attributes.GetStat(PSITEMSTATS_STAT_INTELLIGENCE, false));
                fieldvalues.FormatPush("%d",attributes.GetStat(PSITEMSTATS_STAT_WILL, false));
                fieldvalues.FormatPush("%d",attributes.GetStat(PSITEMSTATS_STAT_CHARISMA, false));

                csString id;
                id = GetCharacterID();

                if(!db->GenericUpdateWithID("characters","id",id,fieldnames,fieldvalues))
                {
                    Error2("Couldn't save stats for character %u!\n",GetCharacterID());
                }
            }
        
            // When a stat is ranked up, hp, mana and stamina are recalculated
            RecalculateStats();
        }
    }
    else
    {
        skills.Train( skill, yIncrease ); // Normal training
        psServer::CharacterLoader.UpdateCharacterSkill(
                GetCharacterID(),
                skill,
                skills.GetSkillPractice((PSSKILL)skill),
                skills.GetSkillKnowledge((PSSKILL)skill),
                skills.GetSkillRank((PSSKILL)skill)
                );
        RecalculateStats();
    }
}


    
/*-----------------------------------------------------------------*/


void Skill::CalculateCosts(psCharacter* user)
{            
    if(!info || !user)
        return;

    // Calc the new Y/Z cost
    MathScript* costScript;
    csString scriptName;

    if(info->id < PSSKILL_AGI || info->id > PSSKILL_WILL)
        scriptName = "CalculateSkillCosts";
    else
        scriptName = "CalculateStatCosts";

    costScript = psserver->GetMathScriptEngine()->FindScript(scriptName);
    if (!costScript)
    {
        Error2("Couldn't find script %s!",scriptName.GetData());
        return;
    }

    // Output
    MathScriptVar*  yCostVar        = costScript->GetVar("YCost");
    MathScriptVar*  zCostVar        = costScript->GetVar("ZCost");

    // Input
    MathScriptVar*  baseCostVar  = costScript->GetOrCreateVar("BaseCost");
    MathScriptVar*  skillRankVar = costScript->GetOrCreateVar("SkillRank");
    MathScriptVar*  skillIDVar   = costScript->GetOrCreateVar("SkillID");
    MathScriptVar*  practiceVar  = costScript->GetOrCreateVar("PracticeFactor");
    MathScriptVar*  mentalVar    = costScript->GetOrCreateVar("MentalFactor");
    MathScriptVar*  actorVar     = costScript->GetOrCreateVar("Actor");

    if(!yCostVar)
    {
        Warning2(LOG_SKILLXP,"Couldn't find the YCost var in %s script!\n",scriptName.GetData());
        return;
    }
    if(!zCostVar)
    {
        Warning2(LOG_SKILLXP,"Couldn't find the ZCost var in %s script!\n",scriptName.GetData());
        return;
    }

    // Input the data
    skillRankVar->SetValue((double)rank);
    skillIDVar->SetValue((double)info->id);
    practiceVar->SetValue((double)info->practice_factor);
    mentalVar->SetValue((double)info->mental_factor);
    baseCostVar->SetValue((double)info->baseCost);
    actorVar->SetObject(user);

    // Execute
    costScript->Execute();

    // Get the output
    yCost = (int)yCostVar->GetValue();
    zCost = (int)zCostVar->GetValue();  
    // Make sure the y values is clamped to the cost.  Otherwise Practice may always
    // fail.
    if  (y > yCost)     
        y = yCost;
    if ( z > zCost )
        z = zCost;        
}

void Skill::Train( int yIncrease )
{
    y+=yIncrease;
    if ( y > yCost )
        y = yCost;        
}        


bool Skill::Practice( unsigned int amount, unsigned int& actuallyAdded,psCharacter* user )
{
    bool rankup = false;
    // Practice can take place
    if ( yCost == y )
    {
        z+=amount;
        if ( z >= zCost )
        {
            rank++;
            z = 0;
            y = 0;                            
            actuallyAdded = z - zCost;
            rankup = true;
            // Reset the costs for Y/Z
            CalculateCosts(user);
        }               
        else
        {
            actuallyAdded = amount;            
        }                                         
    }
    else
    {   
        actuallyAdded = 0;        
    }
    
    return rankup;
}





int FindGlyphSlot(const csArray <glyphSlot_t> & slots, psItemStats * glyphType, int purifyStatus)
{
    int slotNum;

    for (slotNum=0; slotNum < (int)slots.Length(); slotNum++)
        if  (
              (slots[slotNum].glyphType == glyphType)
                &&
              (slots[slotNum].purifyStatus == purifyStatus)
            )
            return slotNum;
    return -1;
}

void psCharacter::CreateGlyphList(bool purifiedOnly, csArray <glyphSlot_t> & slots)
{
    int bulkNum, slotNum;

    for (bulkNum=0; bulkNum < PSCHARACTER_BULK_COUNT; bulkNum++)
    {
        psGlyph * glyph = dynamic_cast <psGlyph*> (inventory.GetBulkItem(bulkNum));
        if (glyph != NULL)
        {
            if (glyph->GetPurifyStatus()==2  ||  !purifiedOnly)
            {
                slotNum = FindGlyphSlot(slots, glyph->GetBaseStats(), glyph->GetPurifyStatus());
                if (slotNum == -1)
                {
                    slotNum = (int)slots.Length();
                    slots.SetLength(slotNum+1);
                    slots[slotNum].glyphType = glyph->GetBaseStats();
                    slots[slotNum].purifyStatus = glyph->GetPurifyStatus();
                    slots[slotNum].count = 0;
                }
                slots[slotNum].count += glyph->GetStackCount();
            }
        }
    }
}

bool psCharacter::HasGlyphs(const glyphList_t & glyphsToCheck)
{
    csArray <glyphSlot_t> glyphsInBulk;
    size_t i;
    int slotNum;

    CreateGlyphList(true, glyphsInBulk);
    for (i=0; i < glyphsToCheck.Length(); i++)
    {
        slotNum = FindGlyphSlot(glyphsInBulk, glyphsToCheck[i], 2);
        if (slotNum == -1)
            return false;
        if (glyphsInBulk[slotNum].count == 0)
            return false;
        glyphsInBulk[slotNum].count --;
    }
    return true;
}

void psCharacter::SetSkillRank( PSSKILL which, int rank)
{
    skills.SetSkillRank(which, rank);

    if (which == PSSKILL_AGI )
        attributes.SetStat(PSITEMSTATS_STAT_AGILITY, rank);
    else if( which == PSSKILL_CHA )
        attributes.SetStat(PSITEMSTATS_STAT_CHARISMA, rank);
    else if( which == PSSKILL_END )
        attributes.SetStat(PSITEMSTATS_STAT_ENDURANCE, rank);
    else if( which == PSSKILL_INT )
        attributes.SetStat(PSITEMSTATS_STAT_INTELLIGENCE, rank);
    else if( which == PSSKILL_STR )
        attributes.SetStat(PSITEMSTATS_STAT_STRENGTH, rank);
    else if( which == PSSKILL_WILL)
        attributes.SetStat(PSITEMSTATS_STAT_WILL,rank);
        
}

unsigned int psCharacter::GetCharLevel()
{
    return ( attributes.GetStat(PSITEMSTATS_STAT_STRENGTH) +
             attributes.GetStat(PSITEMSTATS_STAT_ENDURANCE) +
             attributes.GetStat(PSITEMSTATS_STAT_AGILITY) +
             attributes.GetStat(PSITEMSTATS_STAT_INTELLIGENCE) +
             attributes.GetStat(PSITEMSTATS_STAT_WILL) +
             attributes.GetStat(PSITEMSTATS_STAT_CHARISMA) ) / 6;
}

//This function recalculates Hp, Mana, and Stamina when needed (char creation, combats, training sessions)
void psCharacter::RecalculateStats()
{
    // Calculate current Max Mana level:
    static MathScript *maxManaScript;

    if (!maxManaScript)
    {
        // Max Mana Script isn't loaded, so load it
        maxManaScript = psserver->GetMathScriptEngine()->FindScript("CalculateMaxMana");
        CS_ASSERT(maxManaScript != NULL);
    }
    

    if ( maxManaScript )
    {
        MathScriptVar* actorvar  = maxManaScript->GetOrCreateVar("Actor");
        actorvar->SetObject(this);
        
        if (override_max_mana)
        {
            SetManaMax(override_max_mana);
        }
        else
        {
            maxManaScript->Execute();
            MathScriptVar* manaValue =  maxManaScript->GetVar("MaxMana");
            SetManaMax(manaValue->GetValue());
        }
    }

    // Calculate current Max HP level:
    static MathScript *maxHPScript;
    
    if (!maxHPScript)
    {
        // Max HP Script isn't loaded, so load it
        maxHPScript = psserver->GetMathScriptEngine()->FindScript("CalculateMaxHP");
        CS_ASSERT(maxHPScript != NULL);
    }
    

    if ( maxHPScript )
    {
        MathScriptVar* actorvar  = maxHPScript->GetOrCreateVar("Actor");
        actorvar->SetObject(this);
        
        if (override_max_hp)
        {
            SetHitPointsMax(override_max_hp);
        }
        else
        {
            maxHPScript->Execute();
            MathScriptVar* HPValue =  maxHPScript->GetVar("MaxHP");
            SetHitPointsMax(HPValue->GetValue());    
        }
    }

    // The max weight that a player can carry
    inventory.CalculateStats();
    
    // Stamina
    CalculateMaxStamina();

    // Speed
//    if (GetActor())
//        GetActor()->UpdateAllSpeedModifiers();
}

//TODO: Make this not return a temp csString, but fix in place
csString NormalizeCharacterName(const csString & name)
{
    csString normName = name;
    normName.Downcase();
    normName.Trim();
    if (normName.Length() > 0)
        normName.SetAt(0,toupper(normName.GetAt(0)));
    return normName;
}



unsigned int StatSet::GetStat(PSITEMSTATS_STAT attrib, bool withBuff)
{
    if (attrib<0 || attrib>=PSITEMSTATS_STAT_COUNT)
        return 0;

    int buff   = withBuff?stats[attrib].rankBuff:0;       
    int result = (int)stats[attrib].rank + buff;        
    return result;
}

int StatSet::GetBuffVal( PSITEMSTATS_STAT attrib )
{
    if (attrib<0 || attrib>=PSITEMSTATS_STAT_COUNT)
        return 0;
    
    return stats[attrib].rankBuff;        
}
    

void StatSet::BuffStat( PSITEMSTATS_STAT attrib, int buffAmount )
{
    if (attrib<0 || attrib>=PSITEMSTATS_STAT_COUNT)
        return;

    // should buffed stat go negative, we set it to zero
    if (int(stats[attrib].rank) + stats[attrib].rankBuff + buffAmount < 0)
        stats[attrib].rankBuff = -(int)stats[attrib].rank;
    else
        stats[attrib].rankBuff+=buffAmount;

    self->RecalculateStats();
}

void StatSet::SetStat(PSITEMSTATS_STAT attrib, unsigned int val, bool recalculatestats)
{
    if (attrib<0 || attrib>=PSITEMSTATS_STAT_COUNT)
        return;

    stats[attrib].rank=val;
    if (recalculatestats)
      self->RecalculateStats();
}

void StatSet::AddToStat(PSITEMSTATS_STAT attrib, unsigned int delta)
{
    if (attrib<0 || attrib>=PSITEMSTATS_STAT_COUNT)
        return;

    stats[attrib].rank += delta;        

    self->RecalculateStats();
}



int SkillSet::AddSkillPractice(PSSKILL skill, unsigned int val)
{
    unsigned int added;
    bool rankUp;
    csString name = "";
    
    rankUp = AddToSkillPractice(skill,val, added);
    
    psSkillInfo* skillInfo = CacheManager::GetSingleton().GetSkillByID(skill);

    if ( skillInfo )
    {
        // Save skill and practice only when the level reached a new rank
        // this is done to avoid saving to db each time a player hits
        // an opponent
        if(rankUp && self->GetActor()->GetClientID() != 0)
        {
            psServer::CharacterLoader.UpdateCharacterSkill(
                self->GetCharacterID(),
                skill,
                GetSkillPractice((PSSKILL)skill),
                GetSkillKnowledge((PSSKILL)skill),
                GetSkillRank((PSSKILL)skill)
                );
        }
            

        name = skillInfo->name;
        Debug5(LOG_SKILLXP,self->GetActor()->GetClientID(),"Adding %d points to skill %s to character %s (%d)\n",val,skillInfo->name.GetData(),
            self->GetCharFullName(),
            self->GetActor()->GetClientID());
    }
    else
    {
        Debug4(LOG_SKILLXP,self->GetActor()->GetClientID(),"WARNING! Skill practise to unknown skill(%d) for character %s (%d)\n",
            (int)skill,
            self->GetCharFullName(),
            self->GetActor()->GetClientID());
    }
    
    if ( added > 0 )
    {
        psZPointsGainedEvent evt( self->GetActor(), skillInfo->name, added, rankUp ); 
        evt.FireEvent();
    }

    return added;
}

Skill * SkillSet::GetSkill( PSSKILL which )
{
    if (which<0 || which>=PSSKILL_COUNT)
        return NULL;
    else
        return & skills[which];
}

unsigned int SkillSet::GetBestSkillValue( bool withBuffer )
{
    unsigned int max=0;
    for (int i=0; i<PSSKILL_COUNT; i++)
    {
        PSSKILL skill = skills[i].info->id;
        if(     skill == PSSKILL_AGI ||
                skill == PSSKILL_CHA ||
                skill == PSSKILL_END ||
                skill == PSSKILL_INT ||
                skill == PSSKILL_WILL ||
                skill == PSSKILL_STR )
            continue; // Jump past the stats, we only want the skills

        unsigned int rank = skills[i].rank + (withBuffer?skills[i].rankBuff:0);
        if (rank > max)
            max = rank;
    }
    return max;
}

unsigned int SkillSet::GetBestSkillSlot( bool withBuffer )
{
    unsigned int max = 0;
    unsigned int i = 0;
    for (; i<PSSKILL_COUNT; i++)
    {
        unsigned int rank = skills[i].rank + withBuffer?skills[i].rankBuff:0;
        if (rank > max)
            max = rank;
    }

    if (i == PSSKILL_COUNT)
        return (unsigned int)~0;
    else
        return i;
}

void SkillSet::Calculate()
{
    for ( int z = 0; z < PSSKILL_COUNT; z++ )
    {
        skills[z].CalculateCosts(self);
    }
    self->RecalculateStats();
}


bool SkillSet::CanTrain( PSSKILL skill )
{
    if (skill<0 || skill>=PSSKILL_COUNT)
        return false;
    else    
    {
        return skills[skill].CanTrain();
    }
}

void SkillSet::Train( PSSKILL skill, int yIncrease )
{
    
    if (skill<0 ||skill>=PSSKILL_COUNT)
        return;       
    else
    {
        skills[skill].Train( yIncrease );       
    }                    
}


void SkillSet::SetSkillInfo( PSSKILL which, psSkillInfo* info, bool recalculatestats )
{
    if (which<0 || which>=PSSKILL_COUNT)
        return;
    else
    {        
        skills[which].info = info;
        skills[which].CalculateCosts(self);
    }

    if (recalculatestats)
      self->RecalculateStats();
}

void SkillSet::SetSkillRank( PSSKILL which, int rank, bool recalculatestats )
{
    if (which<0 || which>=PSSKILL_COUNT)
        return;
    skills[which].rank = rank;     
    skills[which].CalculateCosts(self);

    if (recalculatestats)
      self->RecalculateStats();
}

void SkillSet::BuffSkillRank( PSSKILL which, int buff )
{
    if (which<0 || which>=PSSKILL_COUNT)
        return;
    skills[which].Buff( buff );

    self->RecalculateStats();
}

void SkillSet::SetSkillKnowledge( PSSKILL which, int y_value )
{
    if (which<0 || which>=PSSKILL_COUNT)
        return;
    skills[which].y = y_value;     
}


void SkillSet::SetSkillPractice(PSSKILL which,int z_value)
{    

    if (which<0 || which>=PSSKILL_COUNT)
        return;
    skills[which].z = z_value;     
}


bool SkillSet::AddToSkillPractice(PSSKILL skill, unsigned int val, unsigned int& added )
{
    if (skill<0 || skill>=PSSKILL_COUNT)
        return 0;

    bool rankup = false;
    rankup = skills[skill].Practice( val, added, self );
    return rankup;
}


unsigned int SkillSet::GetSkillPractice(PSSKILL skill)
{

    if (skill<0 || skill>=PSSKILL_COUNT)
        return 0;
    return skills[skill].z;    
}


unsigned int SkillSet::GetSkillKnowledge(PSSKILL skill)
{

    if (skill<0 || skill>=PSSKILL_COUNT)
        return 0;
    return skills[skill].y;    
}

unsigned int SkillSet::GetSkillRank( PSSKILL skill, bool withBuffer )
{
    if (skill<0 || skill>=PSSKILL_COUNT)
        return 0;
        
    int buff   = withBuffer?skills[skill].rankBuff:0;       
    int result = (int)skills[skill].rank + buff;        
                 
    return (unsigned int)result;  
}


Skill& SkillSet::Get(PSSKILL skill)
{
    return skills[skill];
}

