/*
* workmanager.cpp
*
* Copyright (C) 2001-2003 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 <math.h>
#include <iutil/object.h>

#include "gem.h"
#include "globals.h"
#include "netmanager.h"
#include "util/psdatabase.h"
#include "psserver.h"
#include "psserverchar.h"
#include "playergroup.h"
#include "net/messages.h"
#include "util/eventmanager.h"
#include "util/serverconsole.h"
#include "entitymanager.h"
#include "weathermanager.h"

#include "bulkobjects/psactionlocationinfo.h"
#include "bulkobjects/pscharacterloader.h"
#include "bulkobjects/pscharacter.h"
#include "bulkobjects/psglyph.h"
#include "bulkobjects/psitem.h"
#include "bulkobjects/psmerchantinfo.h"
#include "bulkobjects/psraceinfo.h"
#include "bulkobjects/pstrade.h"
#include "cachemanager.h"
#include "workmanager.h"
#include "globals.h"
#include "util/mathscript.h"

#define DEBUG_WORKMANAGER

/* 
 *  There are four types of work that can be done:
 *  1. Manufacturing work harvests raw materials.
 *  2. Production work creates new items from raw materials.
 *  3. Lock picking opens locekd containers.
 *  4. Cleanup work removes the ownership of abandoned items.
 *
 *  Go to each section of the code for a full description of each process.
**/


//  To add a new constraint you need to:
//    put a reference to the new constraint function in this struct with constraint string and player message
//    declare a new constraint function in the header like: static bool constraintNew();
//    code the constaint function elsewhere in this module
const constraint constraints[] =
{
    // Time of day.
    // Parameter: hh; where hh is hour of 24 hour clock.
    // Example: TIME(12) is noon.
    psWorkManager::constraintTime, "TIME", "You can not do this work at this time of the day!",

    // People in area.
    // Parameter: n,r; where n is number of people and r is the range.
    // Example: FRIENDS(6,4) is six people within 4.
    psWorkManager::constraintFriends, "FRIENDS", "You need more people for this work!",

    // Location of player.
    // Parameter: x,y,z,r; where x is x-coord, y is y-coord, z is z-coord, and r is rotation.
    // Example: LOCATION(-10.53,176.36,,) is at [-10.53,176.36] any hight and any direction.
    psWorkManager::constraintLocation, "LOCATION","You can not do this work here!",

    // Player mode.
    // Parameter: mode; where mode is psCharacter mode string.
    // Example: MODE(sitting) is player needs to be sitting as work is started.
    psWorkManager::constraintMode, "MODE","You are not in the right position to complete this work!",

    // Array end.
    NULL, "", ""

};

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

psWorkManager::psWorkManager(EntityManager* celserv)
{
    entitymanager = celserv;
    currentQuality = 1.00;

    psserver->GetEventManager()->Subscribe(this,MSGTYPE_WORKCMD,REQUIRE_READY_CLIENT|REQUIRE_ALIVE);
    psserver->GetEventManager()->Subscribe(this,MSGTYPE_LOCKPICK,REQUIRE_READY_CLIENT|REQUIRE_ALIVE|REQUIRE_TARGET);

    script_engine = psserver->GetMathScriptEngine();
    calc_repair_time   = script_engine->FindScript("Calculate Repair Time");
    calc_repair_result = script_engine->FindScript("Calculate Repair Result");

    if (!calc_repair_time)
    {
        Error1("Could not find mathscript 'Calculate Repair Time' in rpgrules.xml");
    }
    else
    {
        var_time_Worker = calc_repair_time->GetVar("Worker");
        var_time_Object = calc_repair_time->GetVar("Object");
        var_time_Result = calc_repair_time->GetVar("Result");

        if (!var_time_Worker || !var_time_Object)
        {
            Error1("Either var 'Worker', 'Object' or 'Result' is missing from Calculate Repair Time script in rpgrules.xml.");
        }
    }

    if (!calc_repair_result)
    {
        Error1("Could not find mathscript 'Calculate Repair Result' in rpgrules.xml");
    }
    else
    {
        var_result_Worker = calc_repair_result->GetVar("Worker");
        var_result_Object = calc_repair_result->GetVar("Object");
        var_result_Result = calc_repair_result->GetVar("Result");

        if (!var_result_Worker || !var_result_Object || !var_result_Result)
        {
            Error1("Either var 'Worker', 'Object' or 'Result' is missing from Calculate Repair Result script in rpgrules.xml.");
        }
    }

    Initialize();
};


psWorkManager::~psWorkManager()
{
    if (psserver->GetEventManager())
    {
        psserver->GetEventManager()->Unsubscribe(this,MSGTYPE_WORKCMD);
        psserver->GetEventManager()->Unsubscribe(this,MSGTYPE_LOCKPICK);
    }
}

void psWorkManager::Initialize()
{
    Result res(db->Select("select * from natural_resources"));

    if (res.IsValid())
    {
        for (unsigned int i=0; i<res.Count(); i++)
        {
            NaturalResource *nr = new NaturalResource;

            nr->sector = res[i].GetInt("loc_sector_id");
            nr->loc.x  = res[i].GetFloat("loc_x");
            nr->loc.y  = res[i].GetFloat("loc_y");
            nr->loc.z  = res[i].GetFloat("loc_z");
            nr->radius = res[i].GetFloat("radius");
            nr->visible_radius = res[i].GetFloat("visible_radius");
            nr->probability = res[i].GetFloat("probability");
            nr->skill = CacheManager::GetSingleton().GetSkillByID( res[i].GetInt("skill") );
            nr->skill_level = res[i].GetInt("skill_level");
            nr->item_cat_id = res[i].GetUInt32("item_cat_id");
            nr->item_quality = res[i].GetFloat("item_quality");
            nr->anim = res[i]["animation"];
            nr->anim_duration_seconds = res[i].GetInt("anim_duration_seconds");
            nr->reward = res[i].GetInt("item_id_reward");
            nr->reward_nickname = res[i]["reward_nickname"];

            resources.Push(nr);
        }
    }
    else
    {
        Error2("Database error loading natural_resources: %s\n",
               db->GetLastError() );
    }
}


void psWorkManager::HandleMessage(MsgEntry* me,Client *client)
{
    switch ( me->GetType() )
    {
        case MSGTYPE_WORKCMD:
        {
            psWorkCmdMessage msg(me);

            if (!msg.valid)
            {
                psserver->SendSystemError(me->clientnum,"Invalid work command.");
                return;
            }
            
            if (msg.command == "/use" )
            {
                HandleUse(client);
            }
            else if (msg.command == "/combine")
            {
                HandleCombine(client);
            }
            else if (msg.command == "/dig")
            {
                HandleProduction(client,"dig",msg.filter);
            }
            else if (msg.command == "/repair")
            {
                HandleRepair(client);
            }
            break;
        }
        
        case MSGTYPE_LOCKPICK:
        {
            gemObject* target = client->GetTargetObject();
            psItem* item = target->GetItem();

            if ( !item )
            {
                Error1("Found gemItem but no psItem was attached!\n");
                return;
            }

            if(!item->GetIsLockable())
            {
                psserver->SendSystemError(me->clientnum,"This object isn't lockable");
                return;
            }

            StartLockpick(client,item);
            break;
        }
    }

}

//-----------------------------------------------------------------------------
// Repair
//-----------------------------------------------------------------------------

/**
* This function handles commands like "/repair" using
* the following sequence of steps.
*
* 1) Make sure client isn't already busy digging, etc.
* 2) Check for repairable item in right hand slot
* 3) Check for required repair kit item in any inventory slot
* 4) Calculate time required for repair based on item and skill level
* 5) Calculate result after repair
* 6) Queue time event to trigger when repair is complete, if not canceled.
*
*/
void psWorkManager::HandleRepair(Client *client)
{
    // Make sure client isn't already busy digging, etc.
    if ( client->GetActor()->GetMode() != PSCHARACTER_MODE_PEACE )
    {
        psserver->SendSystemError(client->GetClientNum(),"You cannot repair anything because you are already busy.");
        return;
    }

    // No stamina checking yet for repairs.  Never discussed.

    // Check for repairable item in right hand slot
    psItem *repairTarget = client->GetCharacterData()->Inventory().GetEquipmentItem(PSCHARACTER_SLOT_RIGHTHAND);
    if (repairTarget==NULL)
    {
      psserver->SendSystemError(client->GetClientNum(),"Wield the item you want to repair in your right hand and try again." );
      return;
    }
    if (repairTarget->GetItemQuality() >= repairTarget->GetMaxItemQuality() )
    {
        psserver->SendSystemError(client->GetClientNum(),"Your %s is in perfect condition.", repairTarget->GetName() );
        return;
    }

    // Check for required repair kit item in any inventory slot
    int toolstatid     = repairTarget->GetRequiredRepairTool();
    psItemStats *tool  = CacheManager::GetSingleton().GetBasicItemStatsByID(toolstatid);
    psItem *repairTool = NULL;
    if (tool)
    {
        int slot = client->GetCharacterData()->Inventory().FindItemInTopLevelBulkWithStats(tool);
        if (slot == PSCHARACTER_SLOT_NONE)
        {
            psserver->SendSystemError(client->GetClientNum(),"You must have a %s to repair your %s.", tool->GetName(), repairTarget->GetName() );
            return;
        }
        repairTool = client->GetCharacterData()->Inventory().GetBulkItem(slot);
    }

    // Calculate if current skill is enough to repair the item
    int rankneeded = repairTarget->GetPrice().GetTotal() / 150;
    // if the item is low cost, allow people to try repair anyway
    if (repairTarget->GetPrice().GetTotal()<300)
        rankneeded = 0;
    int skillid = repairTarget->GetBaseStats()->GetCategory()->repair_skill_id;
    int repairskillrank = client->GetCharacterData()->GetSkills()->GetSkillRank(PSSKILL(skillid));
    if (repairskillrank<rankneeded)
    {
        psserver->SendSystemError(client->GetClientNum(),"This item is too complex for your current repair skill. You cannot repair it." );
        return;
    }

    // Calculate time required for repair based on item and skill level
    var_time_Object->SetObject(repairTarget);
    var_time_Worker->SetObject(client->GetCharacterData());
    calc_repair_time->Execute();
    // If time is less than 20 seconds, cap it to 20 seconds
    int repair_time = (int)var_time_Result->GetValue();
    if (repair_time < 20)
    {
        repair_time = 20;
    }
    csTicks repairDuration = (csTicks)(repair_time * 1000); // convert secs to msec

    // Calculate result after repair
    var_result_Object->SetObject(repairTarget);
    var_result_Worker->SetObject(client->GetCharacterData());
    calc_repair_result->Execute();
    float repairResult = var_result_Result->GetValue();

    // Queue time event to trigger when repair is complete, if not canceled.
    csVector3 dummy = csVector3(0,0,0);
	
    psWorkGameEvent *evt = new psWorkGameEvent(this, client->GetActor(),repairDuration,REPAIR,dummy,NULL,client,repairTarget,repairResult);
    psserver->GetEventManager()->Push(evt);  // wake me up when repair is done

    client->GetActor()->GetCharacterData()->SetTradeWork(evt);

    repairTarget->SetInUse(true);

    psserver->SendSystemInfo(client->GetClientNum(),"You start repairing the %s and continue for %d seconds.",repairTarget->GetName(),repairDuration/1000);
	client->GetActor()->SetMode(PSCHARACTER_MODE_WORK);
}

/**
 * This function handles the conclusion timer of when a repair is completed.
 * It is not called if the event is cancelled.  It follows the following 
 * sequence of steps.
 *
 * 1) The values are all pre-calculated, so just adjust the quality of the item directly.
 * 2) Consume the repair required item, if flagged to do so.
 * 3) Notify the user.
 */
void psWorkManager::HandleRepairEvent(psWorkGameEvent* workEvent)
{
    psItem *repairTarget = workEvent->object;
   
	// We're done work, clear the mode
	workEvent->client->GetActor()->SetMode(PSCHARACTER_MODE_PEACE);
	repairTarget->SetInUse(false);
    // Check for presence of required tool
    int toolstatid     = repairTarget->GetRequiredRepairTool();
    psItemStats *tool  = CacheManager::GetSingleton().GetBasicItemStatsByID(toolstatid);
    psItem *repairTool = NULL;
    if (tool)
    {
        int slot = workEvent->client->GetCharacterData()->Inventory().FindItemInTopLevelBulkWithStats(tool);
        if (slot == PSCHARACTER_SLOT_NONE)
        {
            psserver->SendSystemError(workEvent->client->GetClientNum(),"You must have a %s to repair your %s.", tool->GetName(), repairTarget->GetName() );
            return;
        }
        repairTool = workEvent->client->GetCharacterData()->Inventory().GetBulkItem(slot);
    }
    
    // Now consume the item if we need to
    if (repairTarget->GetRequiredRepairToolConsumed() && repairTool)
    {
        // Always assume 1 item is consume, not stacked
        psItem *item = workEvent->client->GetCharacterData()->Inventory().RemoveBulk(workEvent->client->GetCharacterData()->Inventory().FindItemInTopLevelBulk(repairTool), 1);
        if (item)
        {
            // This message must come first because item is about to be deleted.
            psserver->SendSystemResult(workEvent->client->GetClientNum(),
                                       "You used a %s in your repair work.",
                                       item->GetName());

            CacheManager::GetSingleton().RemoveInstance(item);
            psserver->GetCharManager()->UpdateItemViews( workEvent->client->GetClientNum() );
        }
    }
    else
    {
        // TODO: Implement decay of quality of repair tool here if not consumed.
    }

    // Adjust the quality of the item
    repairTarget->SetItemQuality( repairTarget->GetItemQuality() + workEvent->repairAmount, true);
	// Cap it to max
	if (repairTarget->GetItemQuality() > repairTarget->GetMaxItemQuality())
        repairTarget->SetItemQuality(repairTarget->GetMaxItemQuality(),true);

    psserver->SendSystemResult(workEvent->client->GetClientNum(),
                               "You have repaired your %s to %.0f out of %.0f",
                               repairTarget->GetName(),
                               repairTarget->GetItemQuality(),
                               repairTarget->GetMaxItemQuality());

    printf("Item quality now %1.2f\n", repairTarget->GetItemQuality() );

  // assign practice points
    int skillid = repairTarget->GetBaseStats()->GetCategory()->repair_skill_id;
    psSkillInfo *skill = CacheManager::GetSingleton().GetSkillByID((PSSKILL)skillid);
    if (skill) 
    {
        workEvent->client->GetCharacterData()->GetSkills()->AddSkillPractice((PSSKILL)skillid,1);
    }            

}



//-----------------------------------------------------------------------------
// Production
//-----------------------------------------------------------------------------

/**
 * This function handles commands like "/dig for gold" using
 * the following sequence of steps:
 *
 *   Make sure client isn't already busy digging, etc.
 *   Find closest natural resource
 *   Validate category of equipped item
 *   Calculate time required
 *   Send anim and confirmation message to client
 *   Queue up game event for success
 */
void psWorkManager::HandleProduction(Client *client,const char *type,const char *reward)
{
    if ( !LoadLocalVars(client) )
    {
        return;
    }

    int mode = client->GetActor()->GetMode();

    // Make sure client isn't already busy digging, etc.
    if ( mode != PSCHARACTER_MODE_PEACE )
    {
        psserver->SendSystemError(client->GetClientNum(),"You cannot %s because you are already busy.",type);
        return;
    }
    
    // check stamina
    if ( !CheckStamina( client->GetCharacterData() ) )
    {
        psserver->SendSystemError(client->GetClientNum(),"You cannot %s because you are too tired.",type);
        return;
    }

    // Make sure they specified a resource type
    if (reward == NULL || !strcmp(reward,"")) 
    {
        psserver->SendSystemError(client->GetClientNum(),"Please specify which resource you want to %s for. (/%s <some resource>)",type,type);
        return;
    }

    csVector3 pos;
    
    // Make sure they are not in the same loc as the last dig.
    client->GetActor()->GetLastProductionPos(pos);
    if (SameProductionPosition(client->GetActor(), pos))
    {
        psserver->SendSystemError(client->GetClientNum(),
                                  "You cannot %s in the same place twice in a"
                                  " row.", type);
        return;
    }

    iSector *sector;
    
    client->GetActor()->GetPosition(pos, sector);

    NaturalResource *nr = FindNearestResource(reward,sector,pos);
    if (!nr)
    {
        psserver->SendSystemInfo(client->GetClientNum(),"You don't see a good place to %s.",type);
        return;
    }

    // Validate the skill
    float cur_skill = client->GetCharacterData()->GetSkills()->GetSkillRank((PSSKILL)nr->skill->id);

    // If skill=0, check if it has at least theoretical training in that skill
    if (cur_skill==0) {
        bool fullTrainingReceived = !client->GetCharacterData()->GetSkills()->GetSkill((PSSKILL)nr->skill->id)->CanTrain();
        if (fullTrainingReceived)
           cur_skill=0.7F;
    }

    if (cur_skill <= 0.0)
    {
        psserver->SendSystemInfo(client->GetClientNum(),"You don't have the skill to %s for %s.",type,reward);
        return;
    }

    // Validate category of equipped item
    psItem *item = owner->Inventory().GetEquipmentItem(PSCHARACTER_SLOT_RIGHTHAND);
    if (!item || nr->item_cat_id != item->GetCategory()->id)
    {
        psserver->SendSystemError(client->GetClientNum(),"You don't have a good tool to %s with, equipped in your right hand.",type);
        return;
    }

    // Calculate time required
    int time_req = nr->anim_duration_seconds;

    // Send anim and confirmation message to client and nearby people
    psOverrideActionMessage msg(client->GetClientNum(),client->GetActor()->GetEntity()->GetID(),nr->anim,nr->anim_duration_seconds);
    psserver->GetEventManager()->Multicast(msg.msg, client->GetActor()->GetMulticastClients(),0,PROX_LIST_ANY_RANGE);
    client->GetActor()->SetMode(PSCHARACTER_MODE_WORK);

    client->GetCharacterData()->SetStaminaRegenerationWork(nr->skill->id);
    client->GetActor()->SetProductionStartPos(pos);

    // Queue up game event for success
    psWorkGameEvent *ev = new psWorkGameEvent(this,client->GetActor(),time_req*1000,PRODUCTION,pos,nr,client);
    psserver->GetEventManager()->Push(ev);  // wake me up when digging is done

    client->GetActor()->GetCharacterData()->SetTradeWork(ev);

    psserver->SendSystemInfo(client->GetClientNum(),"You start to %s.",type);
}

// Function used by super client
void psWorkManager::HandleProduction(gemActor *actor,const char *type,const char *reward)
{
    int mode = actor->GetMode();

    // Make sure client isn't already busy digging, etc.
    if ( mode != PSCHARACTER_MODE_PEACE )
    {
        // psserver->SendSystemError(client->GetClientNum(),"You cannot %s because you are already busy.",type);
        Warning3(LOG_SUPERCLIENT,"%s cannot %s because you are already busy.",actor->GetName(),type);
        return;
    }
    
    // check stamina
    if ( !CheckStamina( actor->GetCharacterData() ) )
    {
        // psserver->SendSystemError(client->GetClientNum(),"You cannot %s because you are too tired.",type);
        Warning3(LOG_SUPERCLIENT,"%s cannot %s because you are too tired.",actor->GetName(),type);
        return;
    }


    csVector3 pos;
    iSector *sector;
    
    actor->GetPosition(pos, sector);

    NaturalResource *nr = FindNearestResource(reward,sector,pos);
    if (!nr)
    {
        // psserver->SendSystemInfo(client->GetClientNum(),"You don't see a good place to %s.",type);
        Warning4(LOG_SUPERCLIENT,"%s don't see a good place to %s for %s.",actor->GetName(),type,reward);
        return;
    }

    // Validate the skill
    float cur_skill = actor->GetCharacterData()->GetSkills()->GetSkillRank((PSSKILL)nr->skill->id);

    // If skill=0, check if it has at least theoretical training in that skill
    if (cur_skill==0) {
        bool fullTrainingReceived = !actor->GetCharacterData()->GetSkills()->GetSkill((PSSKILL)nr->skill->id)->CanTrain();
        if (fullTrainingReceived)
           cur_skill=0.7F;
    }

    if (cur_skill <= 0.0)
    {
        //psserver->SendSystemInfo(client->GetClientNum(),"You don't have the skill to %s for %s.",type,reward);
        Warning5(LOG_SUPERCLIENT,"%s(%d) don't have the skill to %s for %s.",
                 actor->GetName(),actor->GetEntity()->GetID(),type,reward);
        return;
    }

    // Validate category of equipped item
    psItem *item = actor->GetCharacterData()->Inventory().GetEquipmentItem(PSCHARACTER_SLOT_RIGHTHAND);
    if (!item || nr->item_cat_id != item->GetCategory()->id)
    {
        // psserver->SendSystemError(client->GetClientNum(),"You don't have a good tool to %s with, equipped in your right hand.",type);
        Warning3(LOG_SUPERCLIENT,"%s don't have a good tool to %s with, equipped in right hand.",actor->GetName(),type);
        return;
    }

    // Calculate time required
    int time_req = nr->anim_duration_seconds;

    // Send anim and confirmation message to client and nearby people
    psOverrideActionMessage msg(0,actor->GetEntity()->GetID(),nr->anim,nr->anim_duration_seconds);
    psserver->GetEventManager()->Multicast(msg.msg, actor->GetMulticastClients(),0,PROX_LIST_ANY_RANGE);

    actor->SetMode(PSCHARACTER_MODE_WORK);

    actor->GetCharacterData()->SetStaminaRegenerationWork(nr->skill->id);
    actor->SetProductionStartPos(pos);

    // Queue up game event for success
    psWorkGameEvent *ev = new psWorkGameEvent(this,actor,time_req*1000,PRODUCTION,pos,nr,NULL);
    psserver->GetEventManager()->Push(ev);  // wake me up when digging is done

    actor->GetCharacterData()->SetTradeWork(ev);

    //psserver->SendSystemInfo(client->GetClientNum(),"You start to %s.",type);
    Debug4(LOG_SUPERCLIENT,0,"%s start to %s for %s",actor->GetName(),type,reward);
}


bool psWorkManager::SameProductionPosition(gemActor *actor,
                                           const csVector3& startPos)
{
    csVector3 pos;
    iSector *sector;
    
    actor->GetPosition(pos, sector);

    return ((startPos - pos).SquaredNorm() < 1);
}

NaturalResource *psWorkManager::FindNearestResource(const char *reward,iSector *sector, csVector3& pos)
{
    NaturalResource *nr=NULL;

    psSectorInfo *playersector= CacheManager::GetSingleton().GetSectorInfoByName(sector->QueryObject()->GetName());
    int sectorid = playersector->uid;

#ifdef DEBUG_WORKMANAGER
    Debug2(LOG_TRADE,0, "Finding nearest resource for %s\n", reward);
#endif

    float mindist = 100000;
    for (size_t i=0; i<resources.Length(); i++)
    {
        NaturalResource *curr=resources[i];
        if (curr->sector==sectorid)
        {
            if (reward && curr->reward_nickname.CompareNoCase( reward ))
            {
                csVector3 diff = curr->loc - pos;
                float dist = diff.Norm();
                // Update nr if dist is less than radius and closer than previus nr or nr isn't found yet
                if (dist < curr->visible_radius && (dist < mindist || nr == NULL))
                {
                    mindist = dist;
                    nr = curr;
                }
            }
        }
    }

#ifdef DEBUG_WORKMANAGER
    if (nr == NULL)
        Debug2(LOG_TRADE,0, "No resource found for %s\n", reward);
#endif

    return nr;
}

void psWorkManager::HandleProductionEvent(psWorkGameEvent* workEvent)
{
    if (!workEvent->worker->gemobj) // Worker has disconnected
        return;

    // Make sure clients are in the same loc as they started to dig.
    if (workEvent->client && !SameProductionPosition(workEvent->client->GetActor(),
                                workEvent->client->GetActor()->GetProductionStartPos()))
    {
        psserver->SendSystemError(workEvent->client->GetClientNum(),
                                  "You were unsuccessful since you moved away "
                                  "from where you started.");
        
        // Actor isn't working anymore.
        workEvent->worker->gemobj->SetMode(PSCHARACTER_MODE_PEACE);
        return;
    }

    psCharacter *workerchar = workEvent->worker->gemobj->GetCharacterData();
    psItem* tool = workerchar->Inventory().GetEquipmentItem(PSCHARACTER_SLOT_RIGHTHAND);
    if (!tool || workEvent->nr->item_cat_id != tool->GetCategory()->id)
    {
        if (workEvent->client)
        {
            psserver->SendSystemInfo(workEvent->worker->gemobj->GetClientID(),"You were unsuccessful since you no longer have the tool.");
        }
        
        workEvent->worker->gemobj->SetMode(PSCHARACTER_MODE_PEACE); // Actor isn't working anymore
        return;
    }

    float roll = psserver->GetRandom();

    // Get player skill value
    float cur_skill = workerchar->GetSkills()->GetSkillRank((PSSKILL)workEvent->nr->skill->id);

    // If skill=0, check if it has at least theoretical training in that skill
    if (cur_skill==0) {
        bool fullTrainingReceived = !workerchar->GetSkills()->GetSkill((PSSKILL)workEvent->nr->skill->id)->CanTrain();
        if (fullTrainingReceived)
            cur_skill = 0.7F; // consider the skill somewhat usable
    }

    // Calculate complexity factor for skill
    float f1 = cur_skill / workEvent->nr->skill_level;
    if (f1 > 1.0) f1 = 1.0; // Clamp value 0..1

    // Calculate factor for tool quality
    float f2 = tool->GetItemQuality() / workEvent->nr->item_quality;
    if (f2 > 1.0) f2 = 1.0; // Clamp value 0..1

    // Calculate factor for distance from center of resource
    csVector3 diff = workEvent->nr->loc - workEvent->position;
    float dist = diff.Norm();
    float f3 = 1 - (dist / workEvent->nr->radius);
    if (f3 < 0.0) f3 = 0.0f; // Clamp value 0..1

    float total = workEvent->nr->probability * f1 * f2 * f3 + 0.1f;

#ifdef DEBUG_WORKMANAGER
    psString debug;
    debug.AppendFmt( "Probability:     %1.3f\n",workEvent->nr->probability);
    debug.AppendFmt( "Skill Factor:    %1.3f\n",f1);
    debug.AppendFmt( "Quality Factor:  %1.3f\n",f2);
    debug.AppendFmt( "Distance Factor: %1.3f\n",f3);
    debug.AppendFmt( "Total Factor:    %1.3f\n",total);
    debug.AppendFmt( "Roll:            %1.3f\n",roll);
    if (workEvent->client)
    {
        Debug1(LOG_COMBAT, workEvent->client->GetClientNum(),debug.GetData());
    }
#endif

    psCharacter *worke = workEvent->worker->gemobj->GetCharacterData();
    if (roll < total)  // successful!
    {
        int ppGained =  worke->AddExperiencePoints(25);

        if (workEvent->client)
        {
            if ( ppGained > 0 )
                psserver->SendSystemInfo(workEvent->client->GetClientNum(),"You gained some experience points and a progression point!");
            else                        
                psserver->SendSystemInfo(workEvent->client->GetClientNum(),"You gained some experience points");
        }
        
        psItemStats *newitem = CacheManager::GetSingleton().GetBasicItemStatsByID(workEvent->nr->reward);
        if (!newitem)
        {
            Bug2("Natural Resource reward item #%d not found!\n",workEvent->nr->reward);
        }
        else
        {
            psItem *item = newitem->InstantiateBasicItem();

            if ( !worke->MoveToInventory(item) )
            {
                Debug5(LOG_ANY,worke->GetCharacterID(),"HandleProductionEvent() could not give item of stat %u (%s) to character %u [%s])",
                    newitem->GetUID(),newitem->GetName(),worke->GetCharacterID(),worke->GetCharName());
         
                if (workEvent->client)
                {
                    // Assume it's full
                    psserver->SendSystemInfo(workEvent->client->GetClientNum(),"You found %s, but you can't carry anymore of it so you dropped it", newitem->GetName() );
                }
            
                worke->DropItem( item );
            }
            else 
            {    
                if (workEvent->client)
                {
                    psserver->GetCharManager()->UpdateItemViews(workEvent->client->GetClientNum());
                    psserver->SendSystemResult(workEvent->client->GetClientNum(),"You got some %s!", newitem->GetName() );
                }
                else
                {
                    Debug3(LOG_SUPERCLIENT,0,"%s got some %s.",workEvent->worker->gemobj->GetName(),newitem->GetName() );
                }
                
            }                

            item->SetLoaded();  // Item is fully created
            item->Save();    // First save

            // No matter if the item could be moved to inventory reset the last position and
            // give out skills. 
            csVector3 pos;
            iSector* sector;
                
            workEvent->worker->gemobj->GetPosition(pos, sector);
                
            // Store the mining position for the next check.
            if (workEvent->client)
            {
                workEvent->client->GetActor()->SetLastProductionPos(pos);
            }
                
            // increase practice in that skill
            psSkillInfo *skill = CacheManager::GetSingleton().GetSkillByID((PSSKILL)workEvent->nr->skill->id);
            if (skill) 
            {
                workerchar->GetSkills()->AddSkillPractice(PSSKILL_MINING,1);
            }            
        }
    }
    else
    {
        int ppGained =  worke->AddExperiencePoints(2);

        if (workEvent->client)
        {
            if ( ppGained > 0 )
                psserver->SendSystemInfo(workEvent->worker->gemobj->GetClientID(),"You gained a little experience and a progression point!");
            else                        
                psserver->SendSystemInfo(workEvent->worker->gemobj->GetClientID(),"You gained a little experience");
            
        
            psserver->SendSystemInfo(workEvent->worker->gemobj->GetClientID(),"You were not successful.");
        } else
        {
            Debug2(LOG_SUPERCLIENT,0,"%s where not successful.",workEvent->worker->gemobj->GetName());
        }
        
    }
    workEvent->worker->gemobj->SetMode(PSCHARACTER_MODE_PEACE); // Actor isn't working anymore
}


//-----------------------------------------------------------------------------
// Manufacture/cleanup
//-----------------------------------------------------------------------------

/*
*    As items are moved into and out of containers the entry functions are called to check
* if work can be done on the items.  Other entry functions are called when /combine or /use
* commands are issued.  Here are the different types of work that can be done:
*
* 1.	Command /use (targeted item) Specific types of items can be targeted and used to change items held 
*       in the hand into other items.  Example:  Steel stock can be transformed into shield pieces by holding 
*       hot stock in one hand and a hammer in the other while targeting an anvil.
* 2.	Command /use (targeted container) Containers can be targeted and used to change items contained into 
*       other items.  Example:  Mechanical mixers can be targeted and used to mix large amounts of batter into dough.
* 3.	Command /combine (targeted container) Containers can be targeted and used to combine items into other items.
*       Example: Coal and iron ore can be combined in a foundry to make super sharp steel.
* 4.	Drag and drop Single items that are put into special auto-transformation containers can be changed into other
*       items.  Example: Heating iron ore in a forge creates iron stock.
*
*    There are three trade DB tables; trade_patterns, trade_combinations, and trade_transofrmations.  The pattern data
* represents the intent of the work.  The patterns are divided into specific patterns that go with the design items
* that are placed in the mind slot and group patterns.  The group patterns allow work to be associated with a group
* of patterns; this saves a lot of effort in creating individual transformations and combinations for similar patterns.
* The patterns table holds a list of patterns, their group pattern if any, and the item ID of the design item.
* The combination table contains all the one to many items that make up a single combination.  Finally the 
* transformation table contains the information needed to transform one item into another.
*
* A set of member variables is used to guide the work process.  These are usually loaded at the start of the 
*    entry functions, before validations functions are called to make sure the player meets the work requirements.  
* In every case two calls are made to IsTransformable(), one checking for a single or personal 
*    transform and one checking for a group transform.
* The IsTransformable() function will then loop through all the transforms associated with the current design item
*    and the current work item looking for a match.  It will check for required equipment, skills, quantities, 
*    training, as well as any specific constraints made on doing the work.
* If a valid transformation is found StartTransformationEvent() is called that creates a work event and 
*    returns control back.  A combination transformation causes the set of combining items into a temporary item 
*    and then creates starts the transformation for the temporary item.
* Depending on the type of work being done more calls IsTransformable() might be made.  For example, when
*    issuing a /use command on an item both the right and left hand are checked for transformations.
* If no transformations are found an appropriate message is displayed to the user.
* Once the work event is fired skill points are collected and some calculations are done to determine any random 
*    changes to the planned result. 
* At this point the item is transformed into a different item and the user is informed of the change.
* If the transformation is caused by an auto-transform container then IsTransformable() is called again to
*    see if a second transformation needs to be started.
*
*/

///////////////////////////////////////////////////////////////////////
// Stop doing work
void psWorkManager::StopWork(Client* client, psItem* item)
{
    // Check client pointer
    if (!client)
    {
        Error1("Bad client pointer in StopWork");
        return;
    }
    // Check for bad item pointer
    if (!item)
    {
        Error1("Bad item pointer in StopWork");
        return;
    }

    // if no event for item then return
    psWorkGameEvent* curEvent = item->GetTransformationEvent();
    if( !curEvent ) 
    {
        return;
    }

    // Check for ending clean up event
    if (curEvent->category == CLEANUP)
    {
#ifdef DEBUG_WORKMANAGER
    CPrintf(CON_DEBUG, "Stopping cleaning up %s item.\n", item->GetName());
#endif

            StopCleanupWork(client, item);
    }
    else
    {
#ifdef DEBUG_WORKMANAGER
    CPrintf(CON_DEBUG, "Player id #%d stopped working on %s item.\n", worker->gemobj->GetPlayerID(), item->GetName());
#endif

        // Handle all the different transformation types
        if( curEvent->GetTransformationType() == TRANSFORMTYPE_AUTO_CONTAINER )
        {
            StopAutoWork(client, item);
        }
        else
        {
            StopUseWork(client);
        }
    }
}

///////////////////////////////////////////////////////////////////////
// Handle /use command
void psWorkManager::HandleUse(Client *client)
{
    // Assign the memeber vars
    if ( !LoadLocalVars(client) )
    {
        return;
    }

    //Check if we are starting or stopping use
    if ( owner->GetMode() != PSCHARACTER_MODE_WORK )
    {
        StartUseWork(client);
    }
    else
    {
        StopUseWork(client);
    }
}

///////////////////////////////////////////////////////////////////////
// Check if possible to do some use work
void psWorkManager::StartUseWork(Client* client)
{
    // Check to see if we have everything we need to do any trade work
    if (!ValidateWork())
    {
        return;
    }

    // Check to see if we have stamina
    if (!CheckStamina( client->GetCharacterData() ))
    {
        return;
    }
    // Check to see if we have pattern
    if (!ValidateMind())
    {
        psserver->SendSystemError(clientNum,"You have nothing in mind.");
        return;
    }

    // Check to see if player has stamina for work
    if (!ValidateStamina(client))
    {
        return;
    }

    // Check for any targeted item or container in hand
    if (!ValidateTarget(client))
    {
        return;
    }

    // Check if the target is container
    if ( workItem->GetIsContainer() )
    {
        // Load item array from the conatiner
        csArray<psItem*> itemArray;
        psContainerIterator* it = new psContainerIterator(workItem);
        while( it->HasNext() )
        {
            // Only check the stuff that player owns
            psItem* item = it->Next();

#ifdef DEBUG_WORKMANAGER
            if (item->GetOwningCharacter())
            {
                CPrintf(CON_DEBUG, "checking container found=%s owner=%s player=%s\n", 
                    item->GetName(), item->GetOwningCharacter()->GetCharName(), owner->GetCharName() );
            }
            else
            {
                CPrintf(CON_DEBUG, "item name=%s id=%u does not have an owner\n", 
                    item->GetName(), item->GetBaseStats()->GetUID());
            }
#endif

            if (item->GetOwningCharacter() == owner )
            {
                itemArray.Push(item);
            }
        }
        delete it;

        // Check for too many items
        if (itemArray.Length() > 1 )
        {
            // Tell the player they are trying to work on too many items
            SendTransformError( clientNum, TRANSFORM_TOO_MANY_ITEMS );
            return;
        }

        // Only one item from container can be transformed at once
        if (itemArray.Length() == 1)
        {
            // Check to see if item can be transformed
            uint32 itemID = itemArray[0]->GetBaseStats()->GetUID();
            int count = itemArray[0]->GetStackCount();
            
            int personal = IsTransformable( patternId, itemID, count );                
            int group    = IsTransformable( groupPatternId, itemID, count );        
            if ( personal == TRANSFORM_MATCH || (groupPatternId > 0 && group==TRANSFORM_MATCH) )  
            {
                // Set up event for transformation
                StartTransformationEvent(
                    TRANSFORMTYPE_CONTAINER, PSCHARACTER_SLOT_NONE, count, itemArray[0]->GetItemQuality(), itemArray[0]);
                psserver->SendSystemOK(clientNum,"You start work on %d %s.", itemArray[0]->GetStackCount(), itemArray[0]->GetName());
                return;
            }
            else
            {
                // The transform could not be created so send the reason back to the user.
                if ( ( group != TRANSFORM_UNKNOWN_ITEM ) && ( group != TRANSFORM_MISSING_ITEM ) )
                    SendTransformError( clientNum, group, itemID, count );
                else
                    SendTransformError( clientNum, personal, itemID, count );
                return;
            }
        }
        // nothing (owned by the player) in the container to use
        else
        {
            SendTransformError( clientNum, TRANSFORM_BAD_USE );
        }
    }

    // check for in hand use
    else
    {
        // Check if player has any transformation items in right hand
        psItem* rhand = owner->Inventory().GetEquipmentItem(PSCHARACTER_SLOT_RIGHTHAND);
        if ( rhand != NULL )
        {
            // Find out if we can do a transformation on items in hand
            uint32 handId = rhand->GetBaseStats()->GetUID();
            int handCount = rhand->GetStackCount();
            int personal = IsTransformable( patternId, handId, handCount );                
            int group    = IsTransformable( groupPatternId, handId, handCount );        
            if ( personal == TRANSFORM_MATCH || (groupPatternId > 0 && group==TRANSFORM_MATCH) )        
            {
                // Set up event for transformation
                StartTransformationEvent(
                    TRANSFORMTYPE_SLOT, PSCHARACTER_SLOT_RIGHTHAND, handCount, rhand->GetItemQuality(), rhand);
                psserver->SendSystemOK(clientNum,"You start work on %d %s.", rhand->GetStackCount(), rhand->GetName());
                return;
            }        
            else
            {
                // The transform could not be created so send the reason back to the user.
                if ( ( group != TRANSFORM_UNKNOWN_ITEM ) && ( group != TRANSFORM_MISSING_ITEM ) )
                {
                    SendTransformError( clientNum, group, handId, handCount );
                    return;
                }
                else if ( ( personal !=  TRANSFORM_UNKNOWN_ITEM ) && ( personal != TRANSFORM_MISSING_ITEM ) )
                {
                    SendTransformError( clientNum, personal, handId, handCount );
                    return;
                }
            }
        }

        // Check if player has any transformation items in left hand
        psItem* lhand = owner->Inventory().GetEquipmentItem(PSCHARACTER_SLOT_LEFTHAND);
        if ( lhand != NULL )
        {
            // Find out if we can do a transformation on items in hand
            uint32 handId = lhand->GetBaseStats()->GetUID();
            int handCount = lhand->GetStackCount();
            int personal = IsTransformable( patternId, handId, handCount );                
            int group    = IsTransformable( groupPatternId, handId, handCount );        
            if ( personal == TRANSFORM_MATCH || (groupPatternId > 0 && group==TRANSFORM_MATCH) )
            {
                // Set up event for transformation
                StartTransformationEvent(
                    TRANSFORMTYPE_SLOT, PSCHARACTER_SLOT_LEFTHAND, handCount, lhand->GetItemQuality(), lhand);
                psserver->SendSystemOK(clientNum,"You start work on %d %s.", lhand->GetStackCount(), lhand->GetName());
                return;
            }
            else
            {
                // The transform could not be created so send the reason back to the user.
                if ( ( group != TRANSFORM_UNKNOWN_ITEM ) && ( group != TRANSFORM_MISSING_ITEM ) )
                {
                    SendTransformError( clientNum, group, handId, handCount );
                    return;
                }
                else if ( ( personal !=  TRANSFORM_UNKNOWN_ITEM ) && ( personal != TRANSFORM_MISSING_ITEM ) )
                {
                    SendTransformError( clientNum, personal, handId, handCount );
                    return;
                }
            }
        }

        // Since either hand can fail to transform normally send general message if we get to here
        SendTransformError( clientNum, TRANSFORM_MISSING_ITEM );
    }
    return;
}

///////////////////////////////////////////////////////////////////////
// Stop doing use work
void psWorkManager::StopUseWork(Client* client)
{
    // Assign the memeber vars
    if ( !LoadLocalVars(client) )
    {
        Error1("Could not load variables for client");
        return;
    }

    // Check for any targeted item or container in hand
    if ( !ValidateTarget(client))
    {
        return;
    }

    // Kill the work event if it exists
    if( worker->gemobj->GetMode() == PSCHARACTER_MODE_WORK )
    {
        worker->gemobj->SetMode(PSCHARACTER_MODE_PEACE);
        psserver->SendSystemOK(clientNum,"You stop working.");
    }
}

///////////////////////////////////////////////////////////////////////
// Handle /combine command
void psWorkManager::HandleCombine(Client *client)
{
    // Assign the memeber vars
    if ( !LoadLocalVars(client) )
    {
        return;
    }

    //Check if we are starting or stopping use
    if ( owner->GetMode() != PSCHARACTER_MODE_WORK )
    {
        StartCombineWork(client);
    }
    else
    {
        StopCombineWork(client);
    }
}

///////////////////////////////////////////////////////////////////////
// Check if possible to do some use work
void psWorkManager::StartCombineWork(Client* client)
{
    // Check to see if we have everything we need to do any trade work
    if (!ValidateWork())
    {
        return;
    }

    // Check to see if we have pattern
    if (!ValidateMind())
    {
        return;
    }

    // Check for any targeted item or container in hand
    if (!ValidateTarget(client))
    {
        return;
    }

    // Check if targeted item is container
    if ( workItem->GetIsContainer() )
    {
        // Find out if anything can be combined in container
        uint32 combinationId = 0;
        int combinationQty = 0;
        if ( IsContainerCombinable( combinationId, combinationQty ) )
        {
            // Find out if we can do a combination transformation
            int personal = IsTransformable( patternId, combinationId, combinationQty );
            int group    = IsTransformable( groupPatternId, combinationId, combinationQty );
            
            if ( personal == TRANSFORM_MATCH || (groupPatternId > 0 && group==TRANSFORM_MATCH) )        
            {                
                // Transform all items in container into the combination item
                psItem* newItem = CombineContainedItem( combinationId, combinationQty, currentQuality, workItem );
                
                if (newItem)
                {

                    // Save the new item
                    newItem->Save();

                    // Set up event for transformation
                    if (workItem->GetCanTransform())
                    {
                        StartTransformationEvent(
                            TRANSFORMTYPE_AUTO_CONTAINER, PSCHARACTER_SLOT_NONE, 
                            combinationQty, currentQuality, newItem);
                    }
                    else
                    {
                        StartTransformationEvent(
                            TRANSFORMTYPE_CONTAINER, PSCHARACTER_SLOT_NONE, 
                            combinationQty, currentQuality, newItem);
                    }
//                    psserver->SendSystemOK(clientNum,"You start work on %d %s.", newItem->GetStackCount(), newItem->GetName());
                    psserver->SendSystemOK(clientNum,"You start to work on combining items.");
                }
                return;
            }
            else
            {
                // The transform could not be created so send the reason back to the user.
                if ( ( group != TRANSFORM_UNKNOWN_ITEM ) && ( group != TRANSFORM_MISSING_ITEM ) )
                    SendTransformError( clientNum, group, combinationId, combinationQty );
                else
                    SendTransformError( clientNum, personal, combinationId, combinationQty );
            }
            return;
        }
/*
/* Note: Until equipped containers are properly supported this code will need to be left commented out.
/*
        // check for in hand containers for combinations
        else
        {
            // Find out if anything can be combined in hands
            uint32 combinationId = 0;
            int combinationQty = 0;
            if ( IsContainerCombinable( combinationId, combinationQty ) )
            {
                // Find out if we can do a combination transformation
                TradePatternMatch personal = IsTransformable( patternId, combinationId, combinationQty );
                TradePatternMatch group    = IsTransformable( groupPatternId, combinationId, combinationQty );
                if ( personal == TRANSFORM_MATCH || (groupPatternId > 0 && group==TRANSFORM_MATCH) )        
                {
                    // Transform all items in container into the combination item
                    psItem* newItem = NULL;
                    CombineContainedItem( combinationId, combinationQty, currentQuality, newItem );

                    // Set up event for transformation
                    StartTransformationEvent(
                        TRANSFORMTYPE_CONTAINER, PSCHARACTER_SLOT_NONE, combinationQty, currentQuality, newItem);
                    psserver->SendSystemOK(clientNum,"You start work on %d %s!", newItem->GetStackCount(), newItem->GetName());
                    return;
                }
                else
                {
                    // The transform could not be created so send the reason back to the user.
                    if ( group !=  TRANSFORM_UNKNOWN_ITEM )
                        SendTransformError( clientNum, group );
                    else
                        SendTransformError( clientNum, personal );        
                }
                return;
            }
        } */
    }

    // otherwise send no combine message to client.
    SendTransformError( clientNum, TRANSFORM_BAD_COMBINATION );
    return;
}

///////////////////////////////////////////////////////////////////////
// Stop doing combine work
void psWorkManager::StopCombineWork(Client* client)
{
    // Assign the memeber vars
    if ( !LoadLocalVars(client) )
    {
        Error1("Could not load variables for client");
        return;
    }

    // Check for any targeted item or container in hand
    if ( !ValidateTarget(client))
    {
        return;
    }

    // Kill the work event if it exists
    if( worker->gemobj->GetMode() == PSCHARACTER_MODE_WORK )
    {
        worker->gemobj->SetMode(PSCHARACTER_MODE_PEACE);
    }

    // Tell the user
    psserver->SendSystemOK(clientNum,"You stop working.");
}

///////////////////////////////////////////////////////////////////////
// Check if possible to do some automatic work
void psWorkManager::StartAutoWork(Client* client, psItem* container, psItem* droppedItem, int count)
{

    // Assign the memeber vars
    if ( !LoadLocalVars(client) )
    {
        return;
    }

    // interupt the first event of the dropped item if any to handle stacking
    psWorkGameEvent* stackEvent = droppedItem->GetTransformationEvent();
    if( stackEvent )
    {
        droppedItem->SetTransformationEvent(NULL);
        stackEvent->Interrupt();
    }

    // We know it is a container so check if it's a transform container
    if ( container->GetCanTransform() )
    {
        // Check to see if we have everything we need to do any trade work
        if (!ValidateWork())
        {
            return;
        }

        // Work item is container into which items were dropped
        workItem = container;
        autoItem = droppedItem;
        uint32 autoID = autoItem->GetBaseStats()->GetUID();

        // Check to see if we have pattern
        int personal = TRANSFORM_UNKNOWN_ITEM;
        int group = TRANSFORM_UNKNOWN_ITEM;
        if (ValidateMind())
        {
            // Verify there is a valad transformation for the item that was dropped
            personal = IsTransformable( patternId, autoID, count );
            group    = IsTransformable( groupPatternId, autoID, count );
            if ( personal == TRANSFORM_MATCH || (groupPatternId > 0 && group==TRANSFORM_MATCH) )
            {
                // Set up event for auto transformation
                StartTransformationEvent(
                    TRANSFORMTYPE_AUTO_CONTAINER, PSCHARACTER_SLOT_NONE, count, autoItem->GetItemQuality(), autoItem);
                psserver->SendSystemOK(clientNum,"You start work on %d %s.", autoItem->GetStackCount(), autoItem->GetName());
                return;
            }

            // The transform could not be created so send the reason back to the user.
            if ( ( group != TRANSFORM_UNKNOWN_ITEM ) && ( group != TRANSFORM_MISSING_ITEM ) )
                SendTransformError( clientNum, group, autoID, count );
            else if ( ( personal !=  TRANSFORM_UNKNOWN_ITEM ) && ( personal != TRANSFORM_MISSING_ITEM ) )
                SendTransformError( clientNum, personal, autoID, count );
        }

        // Find a garbage tranformation for autoitem
        int garbage = IsTransformable( GARBAGE_PATTERNID, autoID, count );                
        if ( garbage == TRANSFORM_MATCH)
        {
            // Set up event for auto transformation
            StartTransformationEvent(
                TRANSFORMTYPE_AUTO_CONTAINER, PSCHARACTER_SLOT_NONE, count, autoItem->GetItemQuality(), autoItem);
            psserver->SendSystemOK(clientNum,"You are not sure what is going to happen to %d %s.", autoItem->GetStackCount(), autoItem->GetName());
            return;
        }       

        // Find a garbage tranformation for unknown item
        garbage = IsTransformable( GARBAGE_PATTERNID, 0, count );                
        if ( garbage == TRANSFORM_MATCH)
        {
            // Set up event for auto transformation
            StartTransformationEvent(
                TRANSFORMTYPE_AUTO_CONTAINER, PSCHARACTER_SLOT_NONE, count, autoItem->GetItemQuality(), autoItem);
            psserver->SendSystemOK(clientNum,"You are not sure what is doing to happen to %d %s.", autoItem->GetStackCount(), autoItem->GetName());
            return;
        }       

        // If no transformations started go ahead and start a cleanup event
        StartCleanupEvent(client, droppedItem, client->GetActor());
    }
}

///////////////////////////////////////////////////////////////////////
// Stop doing automatic work
void psWorkManager::StopAutoWork(Client* client, psItem* autoItem)
{
    // Assign the memeber vars
    if ( !LoadLocalVars(client) )
    {
        Error1("Could not load variables in stopautowork");
        return;
    }

    // Check for proper autoItem
    if ( !autoItem )
    {
        Error1("StopAutoWork does not have a good autoItem pointer.");
        return;
    }

    // Stop the auto item's work event 
    psWorkGameEvent* workEvent = autoItem->GetTransformationEvent();
    if (workEvent)
    {
        // Unless it's some garbage item or if their is nothing in mind let user know
        psItem* designitem = owner->Inventory().GetEquipmentItem( PSCHARACTER_SLOT_MIND );
        if( !workEvent->GetTransformation())
        {
            Error1("StopAutoWork does not have a good transformation pointer.");
        }
        else if( workEvent->GetTransformation()->GetResultId() > 0 && designitem)
        {
            psserver->SendSystemOK(clientNum,"You stop working on %s.", autoItem->GetName());
        }
        autoItem->SetTransformationEvent(NULL);
        workEvent->Interrupt();
    }

    return;
}

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

///////////////////////////////////////////////////////////////////////
// Returns with the result ID and quantity of the combination
//  if work item container has the correct items in the correct amounts
// Note:  This assumes that the combination items array is sorted by
//  resultId and then itemId
bool psWorkManager::IsContainerCombinable(uint32 &resultId, int &resultQty)
{
    // Load item array from the conatiner
    csArray<psItem*> itemArray;
    psContainerIterator* it = new psContainerIterator(workItem);
    while( it->HasNext() )
    {
        // Only check the stuff that player owns
        psItem* item = it->Next();
        if (item->GetOwningCharacter() == owner )
        {
            itemArray.Push(item);

            // While we are here find the item with the least quality
            float quality = item->GetItemQuality();
            if (currentQuality < quality)
            {
                currentQuality = quality;
            }
        }
    }
    delete it;

    //Check if a combination is possible
    return ValidateCombination(itemArray, resultId, resultQty);
}

///////////////////////////////////////////////////////////////////////
// Returns with the result ID and quantity of the combination
//  if player has the correct items in the correct amounts in hand
// Note:  This assumes that the combination items array is sorted by
//  resultId and then itemId
bool psWorkManager::IsHandCombinable(uint32 &resultId, int &resultQty)
{
    // Load item array from the hand slots
    csArray<psItem*> itemArray;

    // Check if player has any items in right hand
    psItem* rhand = owner->Inventory().GetEquipmentItem(PSCHARACTER_SLOT_RIGHTHAND);
    if ( rhand )
    {
        // add item to list
        itemArray.Push(rhand);
    }

    // Check if player has any containers in left hand
    psItem* lhand = owner->Inventory().GetEquipmentItem(PSCHARACTER_SLOT_LEFTHAND);
    if ( lhand )
    {
        // add item to list
        itemArray.Push(lhand);
    }

    //Check if a combination is possible
    if (ValidateCombination(itemArray, resultId, resultQty))
        return true;
    return false;
}

///////////////////////////////////////////////////////////////////////
// Checks to see if every item in list matches every item in a valid combination
bool psWorkManager::ValidateCombination(csArray<psItem*> itemArray, uint32 &resultId, int &resultQty)
{
    // check if player owns anything in conatiner
    size_t itemCount = itemArray.Length();
    if (itemCount != 0)
    {
        // Get all possible combinations for that pattern
        CombinationSet* combArray = CacheManager::GetSingleton().FindCombinationsList(patternId);
        if (combArray == NULL)
        {
            // Check for group pattern combinations
            combArray = CacheManager::GetSingleton().FindCombinationsList(groupPatternId);
            if (combArray == NULL)
            {   
                Debug3( LOG_TRADE, 0,"Failed to find any combinations in patterns %d and %d", patternId, groupPatternId );
                return false;
            }
        }

        //  Go through all of the combinations looking for exact match
        resultId = 0;
        resultQty = 0;
        
        // for all the possible combination construction items in this data set.
        
        for (size_t i=0; i<combArray->set.Length(); i++)
        {
            CombinationConstruction * current = combArray->set[i];
            unsigned int matchCount = 0;
            
            // If the items count match then this is a possible valid combination.
            if ( itemArray.Length() == current->combinations.Length() )
            {
                // for all items we are trying to combine compare with those for the construction set.
                for ( size_t z = 0; z < itemArray.Length(); z++ )
                {
                    uint32 id =  itemArray[z]->GetCurrentStats()->GetUID();
                    int stackQty = itemArray[z]->GetStackCount();
                   
                    // Iterate over the items in the construction set looking for matches.      
                    for( size_t j = 0; j < current->combinations.Length(); j++ )
                    {
                        uint32 combId  = current->combinations[j]->GetItemId();
                        int combMinQty = current->combinations[j]->GetMinQty();
                        int combMaxQty = current->combinations[j]->GetMaxQty();
                                                                             
                        if ( id ==  current->combinations[j]->GetItemId() && 
                             stackQty >= combMinQty && 
                             stackQty <= combMaxQty )
                        {          
                            // We have matched the ids and the stack counts are in range.                
                            matchCount++;                            
                            break;
                        }                            
                    }                    
                }   
                
                // If all items matched up then we have a valid combination
                if ( matchCount == current->combinations.Length() )
                {
                    resultId = current->resultItem;
                    resultQty = current->resultQuantity;    
                    return true;
                }                                             
            }        
        }
    }        
    
    return false;
}


///////////////////////////////////////////////////////////////////////
// Checks if a transformation is possible
int psWorkManager::IsTransformable(uint32 patternId, uint32 targetId, int targetQty)
{
    int match = TRANSFORM_MATCH;
    
    // Only get those with correct target item and pattern
    csPDelArray<psTradeTransformations>* transArray = 
        CacheManager::GetSingleton().FindTransformationsList(patternId, targetId);
    if (transArray == NULL)
    {
        return TRANSFORM_UNKNOWN_ITEM;
    }
    
    // Go thru all the trasnformations and check if one is possible
    Debug2( LOG_TRADE, 0,"Found %d transformations\n", transArray->Length() );
    psItemStats* workStats = workItem->GetCurrentStats();
    psTradeTransformations* transCandidate;
    for (size_t i=0; i<transArray->Length(); i++)
    {
        transCandidate = transArray->Get(i);
        
#ifdef DEBUG_WORKMANAGER
        CPrintf(CON_DEBUG, "trans match attempts ID (%u):\n",transCandidate->GetId());
        uint32 a = transCandidate->GetWorkItemId();
        uint32 b = workStats->GetUID();
        CPrintf(CON_DEBUG, "\tcandidate WorkItemID (%u) == target WorkItemID (%u)\n", a,b);
        CPrintf(CON_DEBUG, "\tcandidate Quantity (%d) == target Quantity(%d)\n", transCandidate->GetItemQty(), targetQty);
        uint32 c =transCandidate->GetEquipementId();
        uint32 d = (owner->Inventory().GetEquipmentItem(PSCHARACTER_SLOT_RIGHTHAND)) ?
            owner->Inventory().GetEquipmentItem(PSCHARACTER_SLOT_RIGHTHAND)->GetCurrentStats()->GetUID() : 0;
        uint32 e = (owner->Inventory().GetEquipmentItem(PSCHARACTER_SLOT_LEFTHAND)) ?
            owner->Inventory().GetEquipmentItem(PSCHARACTER_SLOT_LEFTHAND)->GetCurrentStats()->GetUID() : 0;
        CPrintf(CON_DEBUG, "\tcandidate EquipID(%u) == righthandID(%u) == lefthandID(%u)\n", c,d,e);
#endif

        // Check the work item does not match
        if( transCandidate->GetWorkItemId() != workStats->GetUID() )
        {
                match |= TRANSFORM_MISSING_ITEM;
                continue;
        }

        // Check if any equipement is required
        uint32 equipmentId = transCandidate->GetEquipementId();
        if ( equipmentId > 0 )
        {                
            // Check if required equipment is on hand
            if ( !IsOnHand( equipmentId ) )
            {
#ifdef DEBUG_WORKMANAGER
    CPrintf(CON_DEBUG, "transformation bad equipment id=%u equipmentId=%" PRIu64 "./n", transCandidate->GetId(), equipmentId);
#endif
                match |= TRANSFORM_MISSING_EQUIPMENT;
                continue;
            }
        }

        // Check if we do not have the correct quantity number
        int itemQty = transCandidate->GetItemQty();
        if ( itemQty != 0 && itemQty != targetQty )
        {
            match |= TRANSFORM_BAD_QUANTITY;
            continue;
        }

        // Check if the player has the sufficient training
        if ( !ValidateTraining(transCandidate) )
        {
            match |= TRANSFORM_BAD_TRAINING;
            continue;
        }
        
        // Check if the player has the correct skills
        if ( !ValidateSkills(transCandidate) )
        {
            match |= TRANSFORM_BAD_SKILLS;
            continue;
        }
        
        // Check if the transformation constraint is meet
        if ( !ValidateConstraints(transCandidate) )
        {                
            // Player message comes from database
            match |= TRANSFORM_FAILED_CONSTRAINTS;
            continue;
        }

        // Good match
#ifdef DEBUG_WORKMANAGER
        CPrintf(CON_DEBUG, "Good match: id=(%llu)\n", transCandidate->GetId());
#endif
        trans = transCandidate;
        return TRANSFORM_MATCH;
    }

    // Return all the errors
    return match;
}


///////////////////////////////////////////////////////////////////////
// Do transformation
void psWorkManager::StartTransformationEvent(int transType, int transSlot, int resultQty, float resultQuality, psItem* item )
{
    
    // Get transformation delay
    int delay = CalculateEventDuration( trans->GetTransPoints() );

#ifdef DEBUG_WORKMANAGER
    // Let the server admin know
    CPrintf(CON_DEBUG, "Scheduled player id #%d to finish work in %1.1f seconds.\n", worker->gemobj->GetPlayerID(), (float)delay);
#endif

    // kill old event - just in case
    psWorkGameEvent* oldEvent = item->GetTransformationEvent();
    if (oldEvent)
    {
        Error1("Had to kill old event in StartTransformationEvent.");
        item->SetTransformationEvent(NULL);
        oldEvent->Interrupt();
    }

    // Set event
    csVector3 pos(0,0,0);
    psWorkGameEvent* workEvent = new psWorkGameEvent(
        this, owner->GetActor(), delay*1000, MANUFACTURE, pos, NULL, worker->gemobj->GetClient() );
    workEvent->SetTransformationType(transType);
    workEvent->SetTransformationSlot(transSlot);
    workEvent->SetResultQuality(resultQuality);
    workEvent->SetTransformationItem(item);
    workEvent->SetWorkItem(workItem);
    workEvent->SetResultQuantity(resultQty);
    workEvent->SetTransformation(trans);
    workEvent->SetKFactor(patternKFactor);
    item->SetTransformationEvent(workEvent);

    if ( trans->GetRenderEffect().Length() > 0 )
    {
        csVector3 offset(0,0,0);                  
        workEvent->effectID =  CacheManager::GetSingleton().NextEffectUID();             
        psEffectMessage newmsg(0, "hammer", offset, owner->GetActor()->GetEntity()->GetID(),0,0,workEvent->effectID );    
        newmsg.Multicast(workEvent->multi,0,PROX_LIST_ANY_RANGE);        
    }        

    // If it's not an autotransformation event run the animation and setup the owner with the event
    if( transType != TRANSFORMTYPE_AUTO_CONTAINER)
    {
        // Send anim and confirmation message to client and nearby people
        if( trans->GetAnimation() != "")
        {
            psOverrideActionMessage msg(clientNum, worker->gemobj->GetEntity()->GetID(), trans->GetAnimation(), delay);
            psserver->GetEventManager()->Multicast(msg.msg, worker->gemobj->GetMulticastClients(), 0, PROX_LIST_ANY_RANGE);
        }

        // Setup work owner
        worker->gemobj->SetMode(PSCHARACTER_MODE_WORK);
        owner->SetTradeWork(workEvent);
        owner->SetStaminaRegenerationWork(trans->GetPrimarySkillId());
    }

    // Start event
    psserver->GetEventManager()->Push(workEvent);    
}

///////////////////////////////////////////////////////////////////////
// Loads up local memeber variables
bool psWorkManager::LoadLocalVars(Client* client)
{
    if ( client == NULL )
    {
#ifdef DEBUG_WORKMANAGER
        CPrintf(CON_DEBUG, "Bad client pointer.\n");
#endif
        Debug1(LOG_TRADE,0,"Bad client pointer.");
        return false;
    }

    clientNum = client->GetClientNum();
    if ( clientNum == 0 )
    {
#ifdef DEBUG_WORKMANAGER
        CPrintf(CON_DEBUG, "Bad client number.\n");
#endif
        Debug1(LOG_TRADE,0,"Bad client number.");
        return false;
    }

    worker = client->GetActor()->GetSafeReference();
    if ( !worker || !worker->gemobj )
    {
#ifdef DEBUG_WORKMANAGER
        CPrintf(CON_DEBUG, "Bad client GetActor pointer.\n");
#endif
        Debug1(LOG_TRADE,0,"Bad client GetActor pointer.");
        return false;
    }

    owner = worker->gemobj->GetCharacterData();
    if ( owner == NULL )
    {
#ifdef DEBUG_WORKMANAGER
        CPrintf(CON_DEBUG, "Bad client actor character data pointer.\n");
#endif
        Debug1(LOG_TRADE,0,"Bad client actor character data pointer.");
        return false;
    }

    // Setup mode string pointer
    preworkModeString = owner->GetModeStr();
    
    // Null out everything else
    workItem = NULL;
    patternId = 0;
    groupPatternId = 0;
    patternKFactor = 0.0;
    currentQuality = 0.0;
    trans = NULL;

    return true;
}


///////////////////////////////////////////////////////////////////////
// Check to see if client can do any trade work
bool psWorkManager::ValidateWork()
{
    // Check if not in normal mode
    if ( worker->gemobj->GetMode() == PSCHARACTER_MODE_COMBAT)
    {
        psserver->SendSystemInfo(clientNum,"You can not practice your trade during combat." );
        return false;
    }
    return true;
}



///////////////////////////////////////////////////////////////////////
// Check to see if client can do any trade work and 
// that we have everything we need to do any trade work
bool psWorkManager::ValidateMind()
{
    // Check for the existance of a design item
    psItem* designitem = owner->Inventory().GetEquipmentItem( PSCHARACTER_SLOT_MIND );
    if ( !designitem )
    {
//        psserver->SendSystemError(clientNum,"You have nothing in mind.");
        patternId = 0;
        groupPatternId = 0;
        patternKFactor = 0.0;
        return false;
    }

    // Check if there is a pattern for that design item
    psItemStats* itemStats = designitem->GetCurrentStats();
    psTradePatterns* pattern = CacheManager::GetSingleton().GetTradePatternByItemID( itemStats->GetUID() );
    if ( pattern == NULL )
    {
        Debug3(LOG_TRADE,0,"ValidateWork() could not find pattern ID for item %u (%s) in mind slot",
            itemStats->GetUID(),itemStats->GetName());
        return false;
    }

    // Setup patterns
    patternId = pattern->GetId();
    groupPatternId = pattern->GetGroupPatternId();
    patternKFactor = pattern->GetKFactor();

    return true;
}

///////////////////////////////////////////////////////////////////////
// Check for any targeted item or container in hand
bool psWorkManager::ValidateTarget(Client* client)
{
    // Check if player has something targeted
    gemObject* target = client->GetTargetObject();

    gemActionLocation* gemAction = dynamic_cast<gemActionLocation*>(target);
    if(gemAction) {
      psActionLocation *action = gemAction->GetAction();

      // check if the actionlocation is linked to real item
      int instance_id = action->GetInstanceIDOfContainer();
      if (instance_id==-1)
      {
          instance_id = action->GetGemObject()->GetEntity()->GetID();
      }

      target = psserver->GetEntityManager()->GetGEM()->FindItemEntity( instance_id );

      //gemItem* realItem = dynamic_cast<gemItem*>(realObject);
    }

    if (target)
    {
        // Make sure it's not character
        if (target->GetActorPtr())
        {
            psserver->SendSystemInfo(clientNum,"Only items can be targeted for use.");
            return false;
        }

        // Check range ignoring Y co-ordinate
        if (worker->gemobj->RangeTo(target, true) > RANGE_TO_USE)
        {
            psserver->SendSystemError(clientNum,"You are not in range to use %s.",target->GetItem()->GetName());
            return false;
        }

        // Only legit items
        if (!target->GetItem())
        {
            psserver->SendSystemInfo(clientNum,"That item can not be used in this way.");
            return false;
        }

        // Otherwise assign item
        workItem = target->GetItem();
        return true;
    }

    // if nothing targeted check for containers in hand
    else
    {
        // Check if player has any containers in right hand
        psItem* rhand = owner->Inventory().GetEquipmentItem(PSCHARACTER_SLOT_RIGHTHAND);
        if ( rhand )
        {
            // If it's a container it's our target
            if ( rhand->GetIsContainer() )
            {
                workItem = rhand;
                return true;
            }
        }

        // Check if player has any containers in left hand
        psItem* lhand = owner->Inventory().GetEquipmentItem(PSCHARACTER_SLOT_LEFTHAND);
        if ( lhand )
        {
            // If it's a container it's our target
            if ( lhand->GetIsContainer() )
            {
                workItem = lhand;
                return true;
            }
        }

        // Nothing to do it to
        SendTransformError( clientNum, TRANSFORM_UNKNOWN_WORKITEM );
        return false;
    }
    return false;
}

///////////////////////////////////////////////////////////////////////
// Check if client is too tired
bool psWorkManager::ValidateStamina(Client* client)
{
    // check stamina
    if ( (client->GetActor()->GetCharacterData()->GetStamina(true) < ( client->GetActor()->GetCharacterData()->GetStaminaMax(true)*.3 ) )
     || (client->GetActor()->GetCharacterData()->GetStamina(false) < ( client->GetActor()->GetCharacterData()->GetStaminaMax(false)*.3 )) )
    {
        SendTransformError( clientNum, TRANSFORM_NO_STAMINA );
        return false;
    }
    return true;
}

///////////////////////////////////////////////////////////////////////
// Checks if equipment is in hand
bool psWorkManager::IsOnHand( uint32 equipId )
{
    // Check right hand
    psItem* rhand = owner->Inventory().GetEquipmentItem(PSCHARACTER_SLOT_RIGHTHAND);
    if ( rhand )
    {
        if ( equipId == rhand->GetCurrentStats()->GetUID() )
        {            
            return true;
        }
    }

    // Check left hand
    psItem* lhand = owner->Inventory().GetEquipmentItem(PSCHARACTER_SLOT_LEFTHAND);
    if ( lhand )
    {
        if ( equipId == lhand->GetCurrentStats()->GetUID() )
        {
            return true;
        }
    }
    return false;
}

///////////////////////////////////////////////////////////////////////
// Checks if equipment is already in container
// TODO: not used
bool psWorkManager::IsInContainer( uint32 equipId )
{
    if ( workItem == NULL )
    {
#ifdef DEBUG_WORKMANAGER
        CPrintf(CON_DEBUG, "ERROR: no work item!\n");
#endif
        return false;
    }

#ifdef DEBUG_WORKMANAGER
    CPrintf(CON_DEBUG, "Looking in container for item %u\n", equipId);
#endif
    psContainerIterator* it = new psContainerIterator(workItem);
    while( it->HasNext() )
    {
        psItem* item = it->Next();

#ifdef DEBUG_WORKMANAGER
        CPrintf(CON_DEBUG, "Container has item %u\n", item->GetCurrentStats()->GetUID());
#endif

        if (item->GetCurrentStats()->GetUID() == equipId) {
            delete it;
            return true;
        }
    }
    delete it;
    return false;
}


///////////////////////////////////////////////////////////////////////
// Validates player has enough training
bool psWorkManager::ValidateTraining(psTradeTransformations* transCandidate)
{
    // Check primary skill training if any skill required
    int priSkill = transCandidate->GetPrimarySkillId();
    if ( priSkill > 0 )
    {
        // If primary skill is zero, check if this skill should be trained first
        int basePriSkill = owner->GetSkills()->GetSkillRank((PSSKILL)priSkill);
        if ( basePriSkill == 0 )
        {
            if( owner->GetSkills()->GetSkill((PSSKILL)priSkill)->CanTrain() )
                return false;
        }
    }

    // Check secondary skill training if any skill required
    int secSkill = transCandidate->GetSecondarySkillId();
    if ( secSkill > 0 )
    {
        // If secondary skill is zero, check if this skill should be trained first
        int baseSecSkill = owner->GetSkills()->GetSkillRank((PSSKILL)secSkill);
        if ( baseSecSkill == 0 )
        {
            if( owner->GetSkills()->GetSkill((PSSKILL)secSkill)->CanTrain() )
                return false;
        }
    }
    return true;
}

///////////////////////////////////////////////////////////////////////
// Validates player has the correct skills
bool psWorkManager::ValidateSkills(psTradeTransformations* transCandidate)
{
    // Check if players primary skill levels are less then minimum for that skill
    int priSkill = transCandidate->GetPrimarySkillId();
    if ( priSkill > 0 )
    {
        int minPriSkill = transCandidate->GetMinPrimarySkill();
        int basePriSkill = owner->GetSkills()->GetSkillRank((PSSKILL)priSkill);
        if ( minPriSkill > basePriSkill )
        {
            return false;
        }
    }

    // Check if players secondary skill levels are less then minimum for that skill
    int secSkill = transCandidate->GetSecondarySkillId();
    if ( secSkill > 0 )
    {
        int minSecSkill = transCandidate->GetMinSecondarySkill();
        int baseSecSkill = owner->GetSkills()->GetSkillRank((PSSKILL)secSkill);
        if ( minSecSkill > baseSecSkill )
        {
            return false;
        }
    }
    return true;
}

///////////////////////////////////////////////////////////////////////
bool psWorkManager::CheckStamina(psCharacter * owner) const
{
//todo- use factors based on the work to determine required stamina
    return ((owner->GetStamina(true) > ( owner->GetStaminaMax(true)*.3 ) ) // physical
        || (owner->GetStamina(false) > ( owner->GetStaminaMax(false)*.3 )) //mental
        );
}
///////////////////////////////////////////////////////////////////////
// Validates player is not over skilled
bool psWorkManager::ValidateNotOverSkilled(psTradeTransformations* transCandidate)
{
    // Check if players primary skill levels are less then minimum for that skill
    int priSkill = transCandidate->GetPrimarySkillId();
    if ( priSkill > 0 )
    {
        int maxPriSkill = transCandidate->GetMaxPrimarySkill();
        int basePriSkill = owner->GetSkills()->GetSkillRank((PSSKILL)priSkill);
        if ( maxPriSkill < basePriSkill )
        {
            return false;
        }
    }

    // Check if players secondary skill levels are less then minimum for that skill
    int secSkill = transCandidate->GetSecondarySkillId();
    if ( secSkill > 0 )
    {
        int maxSecSkill = transCandidate->GetMaxSecondarySkill();
        int baseSecSkill = owner->GetSkills()->GetSkillRank((PSSKILL)secSkill);
        if ( maxSecSkill < baseSecSkill )
        {
            return false;
        }
    }
    return true;
}

///////////////////////////////////////////////////////////////////////
// Validate if all the transformation constraints are meet
// Note: This assumes that each of the constraints have paramaters
bool psWorkManager::ValidateConstraints(psTradeTransformations* transCandidate)
{
    // Set up to go through the constraint string picking out the functions and parameters
    const char constraintSeperators[] = " \t(),";
    char constraintPtr[256];
    char* param;

    // Get a work copy of the constraint string to token up
    const char* constraintStr = transCandidate->GetConstraintString();
    strcpy(constraintPtr,constraintStr);

    // Loop through all the constraint strings
    char* funct = strtok( constraintPtr, constraintSeperators);
    while( funct != NULL)
    {
        // Save the pointer to the constraint and get the parameter string
        param = strtok( NULL, constraintSeperators);

        // Check all the constraints
        int constraintId = -1;
        while( constraints[++constraintId].constraintFunction != NULL )
        {
            // Check if the transformation constraint matches
            if( strcmp(constraints[constraintId].name,funct) == 0 )
            {

                // Call the associated function
                if ( !constraints[constraintId].constraintFunction( this, param) )
                {
                    // Send constraint specific message to client
                    psserver->SendSystemError(clientNum, constraints[constraintId].message );
                    return false;
                }
                break;
            }
        }

        // Get the next constraint
        funct = strtok( NULL, constraintSeperators);
    }
    return true;
}

///////////////////////////////////////////////////////////////////////
// Calculate how long it will take to complete event
//  based on the transformation's point quanity
int psWorkManager::CalculateEventDuration(int pointQty)
{
    // Translate the points into seconds
    // ToDo: For now the point quantity is the duration
    return pointQty;
}

///////////////////////////////////////////////////////////////////////
// Combine transform all items in container into a new item
psItem* psWorkManager::CombineContainedItem(uint32 newId, int newQty, float itemQuality, psItem* containerItem)
{
#ifdef DEBUG_WORKMANAGER
    CPrintf(CON_DEBUG, "deleting items from container...\n");
#endif
    // Remove all items in container and destroy them
    unsigned int containerID = workItem->GetGemObject()->GetEntity()->GetID();
    psContainerIterator* it = new psContainerIterator(containerItem);
    while( it->HasNext() )
    {
        // Remove all items from item_instances TODO: this should be only combination items
        psItem* item = it->Next();
        if (item->GetOwningCharacterID() == owner->GetCharacterID() )
        {
            // Kill any trade work started
            psWorkGameEvent* workEvent = item->GetTransformationEvent();
            if (workEvent)
            {
                item->SetTransformationEvent(NULL);
                workEvent->Interrupt();
            }

            // Remove items and delete
            SendClearUpdate( item->GetLocInParent(), containerID );            
            item->TakeOutOfContainer();       
            if (!item->Delete())
            {
                Debug2(LOG_TRADE,containerID,"CombineContainedItem() could not remove old item ID #%u from database.", item->GetUID());
                return NULL;
            }
            delete item;
        }                
    }
    delete it;

    // Create combined item
    psItem* newItem = CreateTradeItem(newId, newQty, itemQuality, containerItem, 0);
    if(!newItem)
    {
#ifdef DEBUG_WORKMANAGER
        CPrintf(CON_DEBUG, "creating new item failed!\n");
#endif
        Debug2(LOG_TRADE,containerID,"StartCombineWork() could not create new item ID #%u", newId);
        return NULL;
    }
#ifdef DEBUG_WORKMANAGER
    CPrintf(CON_DEBUG, "creating new item ok\n");
#endif
    
    return newItem;
}

///////////////////////////////////////////////////////////////////////
// Transform the one items in container into a new item
bool psWorkManager::TransformContainedItem(psItem* oldItem, uint32 newId, int newQty, float itemQuality)
{
    if (!oldItem)
    {
        Debug1(LOG_TRADE,0,"TransformAutoContainedItem() had no item to remove." );
        return false;
    }

#ifdef DEBUG_WORKMANAGER
    CPrintf(CON_DEBUG, "deleting item from container...\n");
#endif

    // Remove items from container and destroy it
    unsigned int containerID = workItem->GetGemObject()->GetEntity()->GetID();
    SendClearUpdate( oldItem->GetLocInParent(), containerID );            
    oldItem->TakeOutOfContainer();       
    if (!oldItem->Delete())
    {
        Debug2(LOG_TRADE,containerID,"TransformContainedItem() could not remove old item ID #%u from database", oldItem->GetUID());
        return false;
    }
    delete oldItem;

    // Check for consumed item
    if (newId <= 0)
        return true;

    // Create item and save it to item instances
    psItem* newItem = CreateTradeItem(newId, newQty, itemQuality, workItem, 0);
    
    if(!newItem)
    {
#ifdef DEBUG_WORKMANAGER
        CPrintf(CON_DEBUG, "creating new item failed!\n");
#endif
        Debug2(LOG_TRADE,containerID,"TransformContainedItem() could not create new item ID #%u", newId);
        return false;
    }

    // Add the new item to the container (adding to the container is supposed to save the item)
    workItem->AddItemToContainer( newItem, owner );
    SendItemUpdate( newItem->GetLocInParent(), newItem );

#ifdef DEBUG_WORKMANAGER
    CPrintf(CON_DEBUG, "creating new item ok\n");
#endif
    return true;
}

/*
///////////////////////////////////////////////////////////////////////
// Transform all items in players hands into a new item
bool psWorkManager::TransformHandItem(uint32 newId, int newQty, float itemQuality)
{
    // Remove all items from both hands and destroy them
    psItem* rhand = owner->Inventory().GetEquipmentItem(PSCHARACTER_SLOT_RIGHTHAND);
    if (!rhand)
    {
        Debug1(LOG_TRADE,"TransformHandItem() could not find item in right hand to remove for combination");
    }
    // Remove items from slot and destroy them
    psItem *oldItem = owner->Inventory().RemoveEquipment(PSCHARACTER_SLOT_RIGHTHAND);
    oldItem->Delete();
    delete oldItem;

    psItem* lhand = owner->Inventory().GetEquipmentItem(PSCHARACTER_SLOT_LEFTHAND);
    if (!lhand)
    {
        Debug1(LOG_TRADE,"TransformHandItem() could not find item in left hand to remove for combination");
    }
    // Remove items from slot and destroy them
    oldItem = owner->Inventory().RemoveEquipment(PSCHARACTER_SLOT_LEFTHAND);
    oldItem->Delete();
    delete oldItem;

    // Create item and save it to item instances
    psItem* newItem = CreateTradeItem(newId, newQty, itemQuality);

    // Add the new item to the right hand slot
    if(newItem)
    {
        owner->Inventory().EquipIn(PSCHARACTER_SLOT_RIGHTHAND, newItem);
    }
    return true;
}
*/

///////////////////////////////////////////////////////////////////////
// Transform all items in container into a new item
bool psWorkManager::TransformSlotItem(int slot, uint32 newId, int newQty, float itemQuality)
{
    // Remove items from slot and destroy it
    psItem *oldItem = owner->Inventory().RemoveEquipment(slot);
    if (!oldItem)
    {
        Debug2(LOG_TRADE,owner->GetCharacterID(),"TransformSlotItem() could not remove item in slot #%i", slot );
        return false;
    }

    psserver->GetCharManager()->SendOutEquipmentMessages(clientNum, slot, oldItem, psEquipmentMessage::DEEQUIP);
    if (!oldItem->Delete())
    {
        Debug2(LOG_TRADE,owner->GetCharacterID(),"TransformSlotItem() could not remove old item ID #%u from database", oldItem->GetUID());
        return false;
    }
    delete oldItem;

    // Check for consumed item
    if (newId <= 0)
        return true;

    // Create item
    psItem* newItem = CreateTradeItem(newId, newQty, itemQuality, NULL, slot);
    if(!newItem)
    {
#ifdef DEBUG_WORKMANAGER
        CPrintf(CON_DEBUG, "creating new item failed!\n");
#endif
        Debug2(LOG_TRADE,owner->GetCharacterID(),"CreateTradeItem() could not create new item ID #%u", newId);
        return false;
    }

    // Add new item to slot
    if ( owner->Inventory().EquipIn(slot, newItem) )
    {
        // Save the new item
        newItem->Save();

        psserver->GetCharManager()->SendOutEquipmentMessages(clientNum, slot, newItem, psEquipmentMessage::EQUIP);
    }
    else
    {
        Error2("Adding new item %u to inventory failed.", newId);
        delete newItem;
    }

#ifdef DEBUG_WORKMANAGER
    CPrintf(CON_DEBUG, "putting new item %s id=%u into slot number=%i\n",newItem->GetName(),newId,slot);
#endif

    return true;
}

///////////////////////////////////////////////////////////////////////
// Return newly created item (NB! Doesn't save!)
psItem* psWorkManager::CreateTradeItem(uint32 newId, int newQty, float itemQuality, psItem* parent, int slot)
{
    Debug3( LOG_TRADE, 0,"Creating new item id(%u) quantity(%d)\n", newId, newQty );

#ifdef DEBUG_WORKMANAGER
        CPrintf(CON_DEBUG, "creating item id=%u qty=%i quality=%f\n",
            newId, newQty, itemQuality);
#endif    

    // Check to make sure it's not a trasnformation that just destroys items
    if (newId > 0)
    {
        // Create a new item including stack count and the calculated quality
        psItemStats* baseStats = CacheManager::GetSingleton().GetBasicItemStatsByID( newId );
        if (!baseStats)
        {
            Debug2(LOG_TRADE,0,"CreateTradeItem() could not get base states for item #%ld", newId );
            return NULL;
        }

        // Make a perminent new item
        psItem* newItem = baseStats->InstantiateBasicItem( true );
        if (!newItem)
        {
            Debug3(LOG_TRADE,0,"CreateTradeItem() could not create item (%s) id #%u",
                baseStats->GetName(), newId );
            return NULL;
        }

        // Set stuff
        newItem->SetItemQuality(itemQuality,false);
        newItem->SetStackCount(newQty);
        newItem->SetOwningCharacter( owner, false);
		newItem->SetDecayResistance(0.5,false);

        if ( parent )
            parent->AddItemToContainer( newItem, owner );

        // Set current player ID and Guild to creator mark
        newItem->SetCrafterID( worker->gemobj->GetPlayerID(),false );
        newItem->SetGuildID( worker->gemobj->GetGuildID(),true );
        
        // Get sector of container to set locations in world
        float xpos,ypos,zpos,yrot;
        psSectorInfo* sectorinfo;
        workItem->GetLocationInWorld( &sectorinfo, xpos, ypos, zpos, yrot );
        newItem->SetLocationInWorld( sectorinfo, 0.00, 0.00, 0.00, 0.00 );

#ifdef DEBUG_WORKMANAGER
        CPrintf(CON_DEBUG, "done creating item crafterID=%i guildID=%i name=%s owner=%p\n",
            worker->gemobj->GetPlayerID(), worker->gemobj->GetGuildID(), owner->GetCharName(), owner);
#endif    
        newItem->SetLoaded();  // Item is fully created

        return newItem;

    }
    return NULL;
}

bool psWorkManager::SendItemUpdate( unsigned int slotID, psItem *newItem )
{
    if ( newItem==NULL )
        return false;

    // Set up message
    csString name( newItem->GetName() );
    csString icon( newItem->GetImageName() );
    unsigned int stackCount = newItem->GetStackCount();
    unsigned int containerID = workItem->GetGemObject()->GetEntity()->GetID();

    // send message
    psViewItemUpdate outgoing( clientNum, containerID, slotID, false, name, icon, stackCount );
    if (outgoing.valid)
    {
        psserver->GetEventManager()->SendMessage(outgoing.msg);
    }
    else
    {
        Bug2("Could not create valid psViewItemUpdate for client %u.\n", clientNum);
        return false;
    }

    return true;
}

bool psWorkManager::SendClearUpdate( unsigned int slotID, unsigned int containerID)
{
    // Set up message
    csString name( "" );
    csString icon( "" );
    unsigned int stackCount = 0;

    // send message
    psViewItemUpdate outgoing( clientNum, containerID, slotID, true, name, icon, stackCount );
    if (outgoing.valid)
    {
        psserver->GetEventManager()->SendMessage(outgoing.msg);
    }
    else
    {
        Bug2("Could not create valid psViewItemUpdate for client %u.\n", clientNum);
        return false;
    }

    return true;
}


///////////////////////////////////////////////////////////////////////
// Constraint functions
#define PSABS(x)    ((x) < 0 ? -(x) : (x))

///////////////////////////////////////////////////////////////////////
// Constraint function to check hour of day
bool psWorkManager::constraintTime(psWorkManager* that,char* param)
{
    // Check paramater pointer
    if(!param)
        return false;

    // Get game hour in 24 hours cycle
    int curTime = psserver->GetWeatherManager()->GetCurrentTime();
    int targetTime = atoi( param );

    if ( curTime == targetTime )
    {
        return true;
    }
    return false;
}

///////////////////////////////////////////////////////////////////////
// Constraint function to check if players are near work item
//  Note: Constraint distance is limited to proximiy list.
bool psWorkManager::constraintFriends(psWorkManager* that, char* param)
{
    // Count proximity player objects
    // ToDo - code
/*
    int count = 0;
    PublishVector& multi = that->worker->GetMulticastClients();
    ClientConnectionSet* clients = psserver->GetEventManager()->GetConnections();
    for (int i=0; i<multi.Length(); i++)
    {
         if (multi[i]->client==0)  // skip the exception client to avoid circularity
            continue;

        Client *c = clients.Find(multi[i]->client);
        if (c && c->IsReady() )
        {
            if (multi[i]->dist < dist)
            {
                if( count++ >= targetCount )
                {
                    return true;
                }
            }
        }
    }
*/
    return true;
}


#define MAXDISTANCE 1
#define MAXANGLE 0.2
///////////////////////////////////////////////////////////////////////
// Constraint function to check location
bool psWorkManager::constraintLocation(psWorkManager* that, char* param)
{
    // Get the current position of client
    csVector3 pos;
    float yrot;
    iSector* sect;
    that->worker->gemobj->GetPosition( pos, yrot, sect );

    // Parse through the constraint parameters
    char* pdest;
    int ch = ',';
    pdest = strchr( param, ch );
    *pdest = '\0';
    char* xloc = param;

    // go to the next parameter
    param = pdest + 1;
    pdest = strchr( param, ch );
    *pdest = '\0';
    char* yloc = param;

    // go to the next parameter
    param = pdest + 1;
    pdest = strchr( param, ch );
    *pdest = '\0';
    char* zloc = param;

    // go to the next parameter
    param = pdest + 1;
    pdest = strchr( param, ch );
    *pdest = '\0';
    char* yrotation = param;

    // go to the next parameter
    param = pdest + 1;
    pdest = strchr( param, ch );
    *pdest = '\0';
    char* sector = param;

    // Skip if no X constraint co-ord specified
    if ( strlen(xloc) != 0 )
    {
        // Check X location
        float x = atof( xloc );
        if ( PSABS(pos.x - x) > MAXDISTANCE )
            return false;
    }

    // Skip if no Y constraint co-ord specified
    if ( strlen(yloc) != 0 )
    {
        // Check Y location
        float y = atof( yloc );
        if ( PSABS(pos.y - y) > MAXDISTANCE )
            return false;
    }

    // Skip if no Z constraint co-ord specified
    if ( strlen(zloc) != 0 )
    {
        // Check Z location
        float z = atof( zloc );
        if ( PSABS(pos.z - z) > MAXDISTANCE )
            return false;
    }

    // Skip if no Yrot constraint co-ord specified
    if ( strlen(yrotation) != 0 )
    {
        // Check Y rotation
        float r = atof( yrotation );
        if ( PSABS(yrot - r) > MAXANGLE )
            return false;
    }

    // Skip if no sector constraint name specified
    if ( strlen(sector) != 0 )
    {
        // Check sector
        if ( strcmp( sector, sect->QueryObject()->GetName()) != 0)
            return false;
    }

    return true;
}

///////////////////////////////////////////////////////////////////////
// Constraint function to check client mode
bool psWorkManager::constraintMode(psWorkManager* that, char* param)
{
    // Check mode string pointer
    if ( !that->preworkModeString )
        return false;
    
    // Check constraint mode to mode before work started
    if ( strcmp( that->preworkModeString, param) != 0 )
        return false;

    return true;
}

///////////////////////////////////////////////////////////////////////
// This handles trade work transformation events
void psWorkManager::HandleWorkEvent(psWorkGameEvent* workEvent)
{
#ifdef DEBUG_WORKMANAGER
    CPrintf(CON_DEBUG, "handling work event...\n");
#endif

    // Load vars
    if ( !LoadLocalVars(workEvent->client) )
    {
#ifdef DEBUG_WORKMANAGER
            CPrintf(CON_DEBUG, "ERROR: Could not load local variables in HandleWorkEvent().\n");
#endif
        return;
    }

    // Get active transformation and then do it
    trans = workEvent->GetTransformation();
    if (!trans)
    {
#ifdef DEBUG_WORKMANAGER
            CPrintf(CON_DEBUG, "ERROR: No valid transformation in HandleWorkEvent().\n");
#endif
            return;
    }

    // Get event data
    workItem = workEvent->GetWorkItem();
    if (!workItem)
    {
#ifdef DEBUG_WORKMANAGER
            CPrintf(CON_DEBUG, "ERROR: No valid transformation item in HandleWorkEvent().\n");
#endif
            return;
    }

    // Set and load
    workItem->SetTransformationEvent(NULL);
    currentQuality = workEvent->GetResultQuality();
    uint32 result = trans->GetResultId();
    int resultQty = trans->GetResultQty();
    int itemQty = trans->GetItemQty();
    
    //  Check for 0 quantity transformations
    if ( itemQty == 0 )
    {
        resultQty = workEvent->GetResultQuantity();
        itemQty = resultQty;
    }

    // Check and apply the skills to quality and practice points
	if ( !ApplySkills(workEvent->GetKFactor(), workEvent->GetTranformationItem()) && trans->GetGarbageId() != 0 )
    {
        result = trans->GetGarbageId();
        resultQty = trans->GetGarbageQty();
    }

    // Handle all the different transformation types
    int transType = workEvent->GetTransformationType();
    switch( transType )
    {
        case TRANSFORMTYPE_SLOT:
        {
            TransformSlotItem(workEvent->GetTransformationSlot(), result, resultQty, currentQuality );
            break;
        }

        case TRANSFORMTYPE_AUTO_CONTAINER:
        case TRANSFORMTYPE_SLOT_CONTAINER:
        case TRANSFORMTYPE_CONTAINER:
        {
            TransformContainedItem(workEvent->GetTranformationItem(), result, resultQty, currentQuality );
            break;
        }

        case TRANSFORMTYPE_UNKNOWN:
        default:
        {
            break;
        }
    }

    // Let the user know the we have done something
    if (result <= 0 )
    {
        psItemStats* itemStats = CacheManager::GetSingleton().GetBasicItemStatsByID( trans->GetItemId() );
        if (itemStats)
        {
            psserver->SendSystemOK(clientNum," %i %s is gone.", itemQty, itemStats->GetName());
        }
        else
        {
            Debug2(LOG_TRADE,clientNum,"HandleWorkEvent() could not get item stats for item ID #%u.", trans->GetItemId());
            return;
        }
    }
    else
    {
        psItemStats* resultStats = CacheManager::GetSingleton().GetBasicItemStatsByID( result );
        if (resultStats)
        {
            psserver->SendSystemOK(clientNum,"You made %i %s.", resultQty, resultStats->GetName() );
        }
        else
        {
            Debug2(LOG_TRADE,clientNum,"HandleWorkEvent() could not get result item stats for item ID #%u.", result);
            return;
        }
    }

    // If there is something left lets see what other damage we can do
    if( (result > 0) )
    {
        // If it's an auto transformation keep going
        if( (transType == TRANSFORMTYPE_AUTO_CONTAINER) )
        {
            // Check to see if we have pattern
            if ( ValidateMind() )
            {
                // Check if there is another transformation possible for the item just created
                int personal = IsTransformable( patternId, result, resultQty );
                int group    = IsTransformable( groupPatternId, result, resultQty );
                if ( personal == TRANSFORM_MATCH || (groupPatternId > 0 && group==TRANSFORM_MATCH) )       
                {
                    // Set up event for new transformation with the same setting as the old
                    StartTransformationEvent(
                        workEvent->GetTransformationType(), workEvent->GetTransformationSlot(), 
                        workEvent->GetResultQuantity(), workEvent->GetResultQuality(), 
                        workEvent->GetTranformationItem() );
                    return;
                }
            }

            // Check if there is a garbage chain tranformation for the result
            int garbage = IsTransformable( GARBAGE_PATTERNID, result, resultQty );                
            if ( garbage == TRANSFORM_MATCH)
            {
                // Set up event for new transformation with the same setting as the old
                StartTransformationEvent(
                    workEvent->GetTransformationType(), workEvent->GetTransformationSlot(), 
                    workEvent->GetResultQuantity(), workEvent->GetResultQuality(), 
                    workEvent->GetTranformationItem() );
                return;
            }

            // Check if there is a garbage tranformation for the chain
            garbage = IsTransformable( GARBAGE_PATTERNID, 0, resultQty );                
            if ( garbage == TRANSFORM_MATCH)
            {
                // Set up event for new transformation with the same setting as the old
                StartTransformationEvent(
                    workEvent->GetTransformationType(), workEvent->GetTransformationSlot(), 
                    workEvent->GetResultQuantity(), workEvent->GetResultQuality(), 
                    workEvent->GetTranformationItem() );
                return;
            }
        }

        // If item in any container clean it up
        if( (transType == TRANSFORMTYPE_AUTO_CONTAINER) ||
            (transType == TRANSFORMTYPE_CONTAINER) )
        {
            // If no transformations started go ahead and start a cleanup event
            StartCleanupEvent(workEvent->client, workEvent->GetTranformationItem(), workEvent->client->GetActor());
        }
    }

    // Make sure we clear out the old event and go to peace
    owner->SetTradeWork(NULL);  // Todo: still need this?
    worker->gemobj->SetMode(PSCHARACTER_MODE_PEACE);
}

// Check and apply skills to quality and practice points
bool psWorkManager::ApplySkills(float factor, psItem* transItem)
{
	// Check for a primary skill
    int priSkill = trans->GetPrimarySkillId();
    if ( priSkill > 0)
    {
		// Get the players skill level using the transformations primary skill 
		int basePriSkill = owner->GetSkills()->GetSkillRank((PSSKILL)priSkill);
        int maxPriSkill = trans->GetMaxPrimarySkill();

        // Get the quality factor for this primary skill
        //  and only use it if in range.
		// This value represents what percentage of the effect of skills should be
		//  applied to the quality calculation for this transformation
        float priQualFactor = (trans->GetPrimaryQualFactor())/100.00;
        if ((priQualFactor > 0.00) && (priQualFactor < 1.00))
        {
            // For quality considerations cap the base skill
            //  at the max skill for this transformation.
            int capPriSkill = ( basePriSkill > maxPriSkill ) ? maxPriSkill : basePriSkill;

            // Calculate the lack of skill as a percentage of the capped skill over the skill range.
            // Since this is a lack of skill percentage subtract it from 1.
            int minPriSkill = trans->GetMinPrimarySkill();
            float priSkillLessPercent = 0;
            if((maxPriSkill-minPriSkill)>0)
            {
                priSkillLessPercent = 1 - ((float)(capPriSkill-minPriSkill)/(float)(maxPriSkill-minPriSkill));
            }

            // Calculate the effect of the quality factor for this skill by the skill level
			// Subtract it as a percentage from the current ingredient quality
			currentQuality = currentQuality - (currentQuality * priSkillLessPercent * priQualFactor);
        }

        // Only give primary experience to those under the max
        if ( basePriSkill < maxPriSkill )
        {
            // Get some practice in
            int priPoints = trans->GetPrimaryPracticePts();
            owner->GetSkills()->AddSkillPractice( (PSSKILL)priSkill, priPoints );
        }
    }

    // Apply the secondary skill if any
    int secSkill = trans->GetSecondarySkillId();
    if ( secSkill > 0)
    {
        int baseSecSkill = owner->GetSkills()->GetSkillRank((PSSKILL)secSkill);
        int maxSecSkill = trans->GetMaxSecondarySkill();

        // Get the quality factor for this secmary skill
        //  and only use it if in range.
        float secQualFactor = (trans->GetSecondaryQualFactor())/100.00;
        if ((secQualFactor > 0.00) && (secQualFactor < 1.00))
        {
            // For quality considerations cap the base skill
            //  at the max skill for this transformation.
            int capSecSkill = ( baseSecSkill > maxSecSkill ) ? maxSecSkill : baseSecSkill;

            // Calculate the lack of skill as a percentage of the capped skill over the skill range.
            // Since this is a lack of skill percentage subtract it from 1.
            int minSecSkill = trans->GetMinSecondarySkill();
            float secSkillLessPercent = 0;
			if((maxSecSkill-minSecSkill) > 0)
			{
 				secSkillLessPercent = 1 - ((float)(capSecSkill-minSecSkill)/(float)(maxSecSkill-minSecSkill));
			}

            // Calculate the effect of the quality factor for this skill by the skill level
            currentQuality = currentQuality - (currentQuality * secSkillLessPercent * secQualFactor);
        }

        // Only give secondary experience to those under the max
        if ( baseSecSkill < maxSecSkill )
        {
            // Get some practice in
            int secPoints = trans->GetSecondaryPracticePts();
            owner->GetSkills()->AddSkillPractice( (PSSKILL)secSkill, secPoints );
        }
    }

	// Add the transfromation items base quality to the current quality as a crafting bonus
	currentQuality = currentQuality + transItem->GetMaxItemQuality();
	
	// Adjust the final quality with the transformation quality factor
	currentQuality = currentQuality * trans->GetItemQualityFactor();
	
	// Randomize the final quality results 
	// Check if there is a garbage item for this transformation first
	//  since there is a chance of setting quality to zero that will result in garbage item
    int garbageItem = trans->GetGarbageId();
    if ( garbageItem > 0 )
    {
		// We are using a logrithmic calculation so that normally there is little qualtiy change
		//  except at the edges of the random distribution.
		// Use a pattern specific factor to determine the curve at the edges
        float roll = psserver->rng->Get();
        float expFactor = factor*log((1/roll)-1);
        currentQuality = currentQuality -((currentQuality*expFactor)/100);
        
        // Make it perfect if it's better then perfect
		float maxQuality = transItem->GetMaxItemQuality();
		if ( currentQuality > 1000)
        {
            currentQuality = 1000;
        }

        // Fail if it's worst then worse
        else if (currentQuality < 0.00)
        {
            currentQuality = 0.00;
            return false;
        }
    }
    return true;
}

void psWorkManager::SendTransformError( uint32_t clientNum, int result, uint32 curItemId, int curItemQty )
{
    csString error("");

    // There is some hierachy to the error codes
    //  for example, bad quantity is only important if no other error is found
    if (result & TRANSFORM_UNKNOWN_WORKITEM)
    {
        psserver->SendSystemError(clientNum, "Nothing has been choosen to use.");
        return;
    }
    if (result & TRANSFORM_FAILED_CONSTRAINTS)
    {
        // Message is sent by constraint logic.
        // error = "The conditions are not right for this to work.";
        return;
    }
    if (result & TRANSFORM_UNKNOWN_PATTERN)
    {
        psserver->SendSystemError(clientNum, "Cannot really think about this.");
        return;
    }
    if (result & TRANSFORM_BAD_TRAINING)
    {
        psserver->SendSystemError(clientNum, "You need some training before you can do this sort of work.");
        return;
    }
    if (result & TRANSFORM_BAD_SKILLS)
    {
        psserver->SendSystemError(clientNum, "This is a bit beyond your skills at the moment.");
        return;
    }
    if (result & TRANSFORM_OVER_SKILLED)
    {
        psserver->SendSystemError(clientNum, "You loose interest in these simple tasks and can not complete the work.");
        return;
    }
    if (result & TRANSFORM_NO_STAMINA)
    {
        psserver->SendSystemError(clientNum, "You are too tired to do this now.");
        return;
    }
    if (result & TRANSFORM_MISSING_EQUIPMENT)
    {
        psserver->SendSystemError(clientNum, "You do not have the correct tools for this sort of work.");
        return;
    }
    if (result & TRANSFORM_BAD_QUANTITY)
    {
        psserver->SendSystemError(clientNum, "This doesn't seem to be the right amount to do anything usefull.");
        return;
    }
    if (result & TRANSFORM_BAD_USE)
    {
        psserver->SendSystemError(clientNum, "There is nothing in this container that you own.");
        return;
    }
    if (result & TRANSFORM_TOO_MANY_ITEMS)
    {
        psserver->SendSystemError(clientNum, "You can only work on one item at a time this way.");
        return;
    }
    if (result & TRANSFORM_BAD_COMBINATION)
    {
        psserver->SendSystemError(clientNum, "You do not have the right amount of the right items to do anything usefull.");
        return;
    }
    if (result & TRANSFORM_MISSING_ITEM)
    {
        psserver->SendSystemError(clientNum, "Not sure what you want to do with this.");
        return;
    }
    if (result & TRANSFORM_UNKNOWN_ITEM)
    {
        psItemStats* curStats = CacheManager::GetSingleton().GetBasicItemStatsByID( curItemId );
        if(curStats)
        {
            psserver->SendSystemError(clientNum, "Not sure what to do with %d %s.", curItemQty, curStats->GetName());
        }
        return;
    }
    return;
}

//-----------------------------------------------------------------------------
// Cleaning up discarded items from public containers
//-----------------------------------------------------------------------------

///////////////////////////////////////////////////////////////////////
// Stop doing cleanup work
void psWorkManager::StopCleanupWork(Client* client, psItem* cleanItem)
{
    // Check for proper autoItem
    if ( !cleanItem )
    {
        Error1("StopCleanupWork does not have a good cleanItem pointer.");
        return;
    }

    // Stop the cleanup item's work event 
    psWorkGameEvent* workEvent = cleanItem->GetTransformationEvent();
    if (workEvent)
    {
        cleanItem->SetTransformationEvent(NULL);
        workEvent->Interrupt();
    }
    return;
}

///////////////////////////////////////////////////////////////////////
// Start doing cleanup
void psWorkManager::StartCleanupEvent(Client* client, psItem* item, gemActor* worker)
{
    
#ifdef DEBUG_WORKMANAGER
    // Let the server admin know
    CPrintf(CON_DEBUG, "Scheduled item id #%d to be cleaned up in %d seconds.\n", item->GetUID(), CLEANUP_DELAY);
#endif

    // Set event
    csVector3 pos(0,0,0);
    psWorkGameEvent* workEvent = new psWorkGameEvent(
        this, worker, CLEANUP_DELAY*1000, CLEANUP, pos, NULL, client );
    workEvent->SetTransformationItem(item);
    item->SetTransformationEvent(workEvent);
    psserver->GetEventManager()->Push(workEvent);    

    psserver->SendSystemOK(clientNum,"You probably should not leave %d %s here.", item->GetStackCount(), item->GetName());
}

///////////////////////////////////////////////////////////////////////
// Remove ownership of item making item free game.
void psWorkManager::HandleCleanupEvent(psWorkGameEvent* workEvent)
{
#ifdef DEBUG_WORKMANAGER
    CPrintf(CON_DEBUG, "handling cleanup workEvent...\n");
#endif

    // Get event item
    psItem* cleanItem = workEvent->GetTranformationItem();
    if ( cleanItem )
    {
        // If this item is a purified glyph, we need to reset it
        if ( cleanItem->GetPurifyStatus() )
            dynamic_cast<psGlyph*>(cleanItem)->UnPurify();

        cleanItem->SetOwningCharacter(NULL, false);
        cleanItem->Save();
        cleanItem->SetTransformationEvent(NULL);
    }
}

//-----------------------------------------------------------------------------
// Lock picking
//-----------------------------------------------------------------------------

void psWorkManager::StartLockpick(Client* client,psItem* item)
{
    // Check if player has a key
    if (client->GetCharacterData()->Inventory().HaveKeyForLock(item->GetUID()))
    {
        bool locked = item->GetIsLocked();
        psserver->SendSystemInfo(client->GetClientNum(),locked ? "You unlocked %s" : "You locked %s", item->GetName());
        item->SetIsLocked(!locked);
        return;
    }

    if (!CheckStamina( client->GetCharacterData() ))
    {
        psserver->SendSystemInfo(client->GetClientNum(),"You are too tired to lockpick");
        return;
    }

    client->GetCharacterData()->SetStaminaRegenerationWork(PSSKILL_LOCKPICKING);

    psserver->SendSystemInfo(client->GetClientNum(),"You started lockpicking %s",item->GetName());
    client->GetActor()->SetMode(PSCHARACTER_MODE_WORK);

    // Execute mathscript to get lockpicking time
    MathScript* pickTime = psserver->GetMathScriptEngine()->FindScript("Lockpicking Time");
    MathScriptVar* quality = pickTime->GetVar("LockQuality");
    MathScriptVar* time = pickTime->GetVar("Time");

    quality->SetValue(item->GetItemQuality());
    pickTime->Execute();

    // Add new event
    csVector3 emptyV = csVector3(0,0,0);
    psWorkGameEvent *ev = new psWorkGameEvent(
        this,
        client->GetActor(),
        (int) time->GetValue(),
        LOCKPICKING,
        emptyV,
        0,
        client,
        item);
    psserver->GetEventManager()->Push(ev);
}

void psWorkManager::LockpickComplete(psWorkGameEvent* workEvent)
{
    psCharacter* character = workEvent->client->GetCharacterData();
    PSSKILL skill = workEvent->object->GetLockpickSkill();

    // Check if the user has the right skills
    if(character->GetSkills()->GetSkillRank(skill) >= workEvent->object->GetLockStrength())
    {
        bool locked = workEvent->object->GetIsLocked();
        psserver->SendSystemInfo(workEvent->client->GetClientNum(),locked ? "You unlocked %s!" : "You locked %s!", workEvent->object->GetName());
        workEvent->object->SetIsLocked(!locked);
    }
    else
    {
        // Send denied message
        csString text;
        if( workEvent->object->GetIsLocked() )
            text = "unlock";
        else
            text = "lock";

        psserver->SendSystemInfo(
            workEvent->client->GetClientNum(),
            "You need to have at least level %d lockpicking to %s %s",
            workEvent->object->GetLockStrength(),
            text.GetData(),
            workEvent->object->GetName());
    }

    workEvent->client->GetActor()->SetMode(PSCHARACTER_MODE_PEACE);
}


//-----------------------------------------------------------------------------
// Events
//-----------------------------------------------------------------------------

//////////////////////////////////////////////////////////////
//  Primary work object
psWorkGameEvent::psWorkGameEvent(psWorkManager* mgr,
                                 gemActor* worker,
                                 int delayticks,
                                 int cat,
                                 csVector3& pos,
                                 NaturalResource *natres,
                                 Client *c,
                                 psItem* object,
                                 float repairAmt)
                                 : psGameEvent(0,delayticks,"psWorkGameEvent")
{
    workmanager = mgr;
    nr          = natres;
    client      = c;
    position    = pos;
    category    = cat;
    this->object = object;
    this->worker = worker->GetSafeReference();
    worker->RegisterCallback(this);
    repairAmount = repairAmt;
}


void psWorkGameEvent::Interrupt()
{
    // Setting the character mode ends up calling this function again, so we have to check for reentrancy here
    if (workmanager)
    {
        workmanager = NULL;

        // TODO: get tick count so far before killing event
        //  for partial trade work - store it in psItem
        //  leave work state alone so it can be restarted
        
        // Stop event from being executed when triggered.
	    if(category == REPAIR && client->GetActor() && (client->GetActor()->GetMode() == PSCHARACTER_MODE_WORK)) 
	       client->GetActor()->SetMode(PSCHARACTER_MODE_PEACE);
    }
}


// Event trigger
void psWorkGameEvent::Trigger()
{
    if(workmanager && worker->gemobj)
    {
    
        if ( effectID != 0 )
        {        
            psStopEffectMessage newmsg(effectID);                
            newmsg.Multicast(multi,0,PROX_LIST_ANY_RANGE);
        }            
                         

        // Work done for now
        switch(category)
        {
            case(REPAIR):
            {
                workmanager->HandleRepairEvent(this);
                break;
            }
            case(MANUFACTURE):
            {
                workmanager->HandleWorkEvent(this);
                break;
            }
            case(PRODUCTION):
            {
                workmanager->HandleProductionEvent(this);
                break;
            }
            case(LOCKPICKING):
            {
                workmanager->LockpickComplete(this);
                break;
            }
            case(CLEANUP):
            {
                workmanager->HandleCleanupEvent(this);
                break;
            }
            default:
                Debug1(LOG_TRADE,0,"Unknown transformation type!");
                break;
        }
    }
}

void psWorkGameEvent::DeleteObjectCallback(iDeleteNotificationObject * object)
{
    if(workmanager && worker->gemobj)
    {
        worker->gemobj->UnregisterCallback(this);

        // Cleanup any autoitem associated with event
        if( transType == TRANSFORMTYPE_AUTO_CONTAINER )
        {
            if( item )
            {

#ifdef DEBUG_WORKMANAGER
CPrintf(CON_DEBUG, "Cleaning up item from auto-transform container...\n");
#endif

                // Make item ownerless
                item->SetOwningCharacter(NULL, false);
                item->Save();

                // clear out event
                item->SetTransformationEvent(NULL);

            }
            Interrupt();
        }
        else
        {
            // Cleanup any contained item associated with event
            if( transType == TRANSFORMTYPE_CONTAINER )
            {
                if( item )
                {

#ifdef DEBUG_WORKMANAGER
CPrintf(CON_DEBUG, "Cleaning up item from auto-transform container...\n");
#endif

                    // Make item ownerless
                    item->SetOwningCharacter(NULL, false);
                    item->Save();
                }
            }
            // Setting character to peace will interupt work event
            worker->gemobj->GetCharacterData()->SetTradeWork(NULL);
            worker->gemobj->SetMode(PSCHARACTER_MODE_PEACE);
        }
    }
}

psWorkGameEvent::~psWorkGameEvent()
{
    if(worker && worker->gemobj)
    {
        worker->gemobj->UnregisterCallback(this);
        if (worker->gemobj->GetCharacterData()->GetTradeWork() == this)
            worker->gemobj->GetCharacterData()->SetTradeWork(NULL);
    }

    // For manufactuing events only
    if( category == MANUFACTURE )
    {
        // Remove any item reference to this  event
        psItem* transItem = GetTranformationItem();
        if( transItem && transItem->GetTransformationEvent() == this)
            transItem->SetTransformationEvent(NULL);

        // The garbase transformation is not cached and needs to be cleaned up
        if(transformation && !transformation->GetTransformationCacheFlag() )
        {
            delete transformation;
        }
    }
}
