/*
 * progressionmanager.cpp
 *
 * Copyright (C) 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 <stdlib.h>
#include <iutil/document.h>
#include <csutil/xmltiny.h>
#include <iutil/object.h>

#include "globals.h"

#include "clients.h"
#include "usermanager.h"
#include "rpgrules/factions.h"
#include "util/eventmanager.h"
#include "progressionmanager.h"
#include "entitymanager.h"
#include "util/log.h"
#include "util/serverconsole.h"
#include "util/mathscript.h"
#include "psserver.h"
#include "cachemanager.h"
#include "events.h"
#include "bulkobjects/pscharacterloader.h"
#include "bulkobjects/pscharacter.h"
#include "spellmanager.h"
#include "combatmanager.h"
#include "weathermanager.h"
#include "playergroup.h"
#include "gem.h"
#include "util/psxmlparser.h"
#include "bulkobjects/psitem.h"
#include "bulkobjects/pstrainerinfo.h"
#include "bulkobjects/psraceinfo.h"
#include "npcmanager.h"
#include "net/messages.h"
#include "net/npcmessages.h"
#include "util/skillcache.h"
#include "psbehave/psworld.h"

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

class ScriptOp;


class psScriptGameEvent : public psGEMEvent
{
public:
    psScriptGameEvent(csTicks offsetticks,
                      ProgressionManager *mgr,
                      ProgressionEvent * script,
                      gemActor *actor,
                      gemObject *target,
                      bool persistent);

    virtual void DeleteObjectCallback(iDeleteNotificationObject * object);

    void Trigger();
    
protected:
    ProgressionManager *mgr;
    ProgressionEvent * script;
    bool persistent;
    csRef<gemObjectSafe> actor;
    csRef<gemObjectSafe> target;
    bool disconnected;
};

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

class ProgressionOperation
{
protected:
    MathScript *value_script;
    MathScriptVar *valuevar,*targetvar,*actorvar;
    csString script_text,delay_text,result_var_name;
    ProgressionEvent *my_script;
    
    MathScript *delay_script;
    MathScriptVar *delayvar,*delaytargetvar,*delayactorvar;

    float result;


    bool LoadValue(iDocumentNode *node, ProgressionManager *mgr,ProgressionEvent *script);
    float GetValue(gemActor * actor, gemObject *target, ProgressionManager *mgr);
    bool LoadDelay(iDocumentNode *node, ProgressionManager *mgr,ProgressionEvent *script);
    float GetDelay(gemActor * actor, gemObject *target, ProgressionManager *mgr);
    
public:
    csString * eventName;
    ProgressionOperation() { my_script=NULL; valuevar=targetvar=actorvar=NULL; value_script=delay_script=NULL; result =  0.0F;}
    virtual ~ProgressionOperation() {if (value_script) delete value_script; if (delay_script) delete delay_script; }
    virtual bool Run(gemActor * actor, gemObject *target, ProgressionManager *mgr, bool inverse)=0;
    virtual bool Load(iDocumentNode *node, ProgressionManager *mgr,ProgressionEvent *script)=0;
    virtual csString ToString()=0;
    MathScript *GetMathScript() { return value_script; }
    virtual void LoadVariables(csArray<MathScriptVar*> & variables);
    virtual float GetResult() { return result; };
};

void ProgressionOperation::LoadVariables(csArray<MathScriptVar*> & variables)
{
    MathScriptVar *var;
    size_t i;
    
    for (i=0; i<variables.Length(); i++)
    {
        if (value_script)
        {
            var = value_script->GetOrCreateVar(variables[i]->name);
            var->SetValue(variables[i]->GetValue() );
        }
        
        if (delay_script)
        {
            var = delay_script->GetOrCreateVar(variables[i]->name);
            var->SetValue(variables[i]->GetValue() );
        }
    }
}

bool ProgressionOperation::LoadValue(iDocumentNode *node, ProgressionManager *mgr,ProgressionEvent *prg_script)
{
    // check if value is valid
    if (node->GetAttributeValue("value")==NULL || node->GetAttributeValue("value")=="") {
        Error1("Script MUST contain an attribute called \"Value\"");
        return false;
    }

    csString script("Value = ");
    script.Append(node->GetAttributeValue("value"));
    script_text = node->GetAttributeValue("value");  // save for persisting later

    value_script = new MathScript(prg_script->name.GetData(),script);

    valuevar = value_script->GetVar("Value");  // always required and supplied
    targetvar = value_script->GetOrCreateVar("Target");
    actorvar  = value_script->GetOrCreateVar("Actor");

    my_script = prg_script;

    const char *varname = node->GetAttributeValue("save");
    if (varname)
    {
        if (!prg_script->FindVariable(varname))
        {
            MathScriptVar *pv = new MathScriptVar;
            pv->name = varname;
            pv->SetValue(0);
            prg_script->AddVariable(pv);
        }
        result_var_name=varname;
    }
    return true;
};

bool ProgressionOperation::LoadDelay(iDocumentNode *node, ProgressionManager *mgr,ProgressionEvent *prg_script)
{
    // check if value is valid
    if (node->GetAttributeValue("delay") != NULL)
        delay_text = node->GetAttributeValue("delay");
    else
        delay_text = "0";

    csString script("Delay = ");
    script.Append(delay_text);
    
    delay_script = new MathScript(prg_script->name.GetData(),script);

    delayvar = delay_script->GetVar("Delay");  // always required and supplied
    delaytargetvar = delay_script->GetOrCreateVar("Target");
    delayactorvar  = delay_script->GetOrCreateVar("Actor");
    return true;
};

float ProgressionOperation::GetValue(gemActor * actor, gemObject *target, ProgressionManager *mgr)
{
    if (!value_script)
    {
        Error2("Invalid value script in Progression Event '%s'.",this->eventName->GetData() );
        return 0.0;
    }
    targetvar->SetObject(target ? target->GetCharacterData() : NULL);            
    actorvar->SetObject(actor ? actor->GetCharacterData() : NULL );

    value_script->Execute();
    
    if (result_var_name.Length())
    {
        MathScriptVar *pv = my_script->FindVariable(result_var_name);
        pv->SetValue(valuevar->GetValue());
    }

    return valuevar->GetValue();
}

float ProgressionOperation::GetDelay(gemActor * actor, gemObject *target, ProgressionManager *mgr)
{
    if (!delay_script)
    {
        Error2("Invalid delay script in Progression Event '%s'.",this->eventName->GetData() );
        return 0.0;
    }
    delaytargetvar->SetObject(target ? target->GetCharacterData() : NULL);            
    delayactorvar->SetObject(actor ? actor->GetCharacterData() : NULL);

    delay_script->Execute();
    
    return delayvar->GetValue();
}


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

/** Used to create a change in character traits.
  * This is used to change a character's appearance live in game.
  * It takes the trait ID number and sends a broadcast out to all 
  * the players in range about the change.
  *
  * A Trait op is simply <trait value="key into traits table" />
  *
  * On the Run it checks to make sure the selected trait is allowed 
  * for that race and rejects with an error message if it is not.  
  */
class TraitChangeOp : public ProgressionOperation
{
protected:
    int traitID;
    
public:
    TraitChangeOp() : ProgressionOperation() {}
    virtual ~TraitChangeOp() {}
    
    bool Load( iDocumentNode* node, ProgressionManager* mgr, ProgressionEvent* script )
    {
        traitID = node->GetAttributeValueAsInt( "value" );            
        return LoadValue( node, mgr, script );
    }
    
    virtual csString ToString()
    {
        csString xml;           
        xml.Format("<trait value=\"%d\" />", traitID );
        return xml;
    }

    bool Run( gemActor * actor, gemObject* target, ProgressionManager *mgr, bool inverse )
    {
        // Remove this when adding support for the inverse operation
        if (inverse)
            return true;

        // Cannot be run if there is no actor
        if (!actor)
        {
            Error2("Error: ProgressionEvent(%s) TraitChangeOp needs an actor\n",eventName->GetData());
            return true;
        }

        int clientID = actor->GetClientID();
        psCharacter *data = actor->GetCharacterData();
        psRaceInfo* raceInfo = data->GetRaceInfo();

        psTrait* trait = CacheManager::GetSingleton().GetTraitByID( traitID );
        
        // Validate that the selected trait can be applied to the player's race. 
        if ( trait->raceID != raceInfo->uid )
        {
            psserver->SendSystemInfo(clientID,"Doesn't work on you, try a better brand.");
            return false;
        }
        
        data->SetTraitForLocation( trait->location, trait );  
        
        // Send out updated information to all clients on prox. list
        csString str( "<traits>" );
        str.Append(trait->ToXML() );
        str.Append("</traits>");        
        psTraitChangeMessage message( (uint32_t)clientID, (uint32_t)actor->GetEntity()->GetID(), str );
        message.Multicast( actor->GetMulticastClients(), 0, PROX_LIST_ANY_RANGE );                           
        return true;
    }
};


/**
 * Adjust the experience of the target.
 */
class ExperienceOp : public ProgressionOperation
{
protected:
    csString type;

public:
    ExperienceOp() : ProgressionOperation() { };
    virtual ~ExperienceOp() {};

    bool Load(iDocumentNode *node, ProgressionManager *mgr,ProgressionEvent *script)
    {
        type = node->GetAttributeValue("type");
        return LoadValue(node,mgr,script);
    }

    virtual csString ToString()
    {
        csString xml;
        xml.Format("<exp type=\"%s\" value=\"%s\" />",type.GetData(),script_text.GetData() );
        return xml;
    }
    
    
    float AllocateKillDamage(gemObject *target, ProgressionManager *mgr,int exp)
    {
        // Convert to gemActor
        gemActor* targetAct = target->GetActorPtr();
        if(!targetAct)
            return 0.0f;

        csArray<gemActor*> attackers;
        unsigned int timeOfDeath = csGetTicks(); // TODO: Should be recorded on death
                                                 //       in the targetAct.

        // Last timestamp, used for breaking the loop when > 10 secs had gone
        unsigned int lastTimestamp = 0;
        float        totalDamage   = 0; // The denominator for the percentages

        // First build list of attackers and determine how far to go back and what total dmg was.
        int i;
        for (i=(int)targetAct->GetDamageHistoryCount(); i>0; i--)
        {
            DamageHistory* history = targetAct->GetDamageHistory(i-1);

            // 10 secs passed
            if(lastTimestamp - history->timestamp > 15000 && lastTimestamp != 0)
            {
                Debug1(LOG_COMBAT, 0,"15 secs passed between hits, breaking loop\n");
                break;
            }
            lastTimestamp = history->timestamp;
            
            // Special check for DoT adjustments if the target died before the DoT expired
            if(history->damageRate != 0)
            {
                csTicks duration = -(int)(history->damage/history->damageRate);
                if (duration > timeOfDeath - history->timestamp)
                    // Since damageRate should always be negative:
                    history->damage = -(history->damageRate * (timeOfDeath - history->timestamp));
            }

            totalDamage += history->damage;

            bool found = false;

            if (!history->attacker_ref->gemobj)
                continue;  // This attacker has disconnected since he did this damage.

            // Have we already added that player?
            for(size_t x=0;x < attackers.Length();x++)
            {
                if(attackers[x] == history->attacker_ref->gemobj)
                {
                    found = true;
                    break;
                }
            }

            // New player, add to list
            if(!found)
            {
                attackers.Push(dynamic_cast<gemActor*>(history->attacker_ref->gemobj)); // This is ok because it is ONLY used in this function
            }
        }
        int lastHistory = i;

        for(size_t i=0;i < attackers.Length();i++)
        {
            gemActor* attacker = attackers[i];
            if(!attacker)
                continue;  // should not happen with new safe ref system.

            float dmgMade = 0;
            float mod = 0;           

            for (int x = (int)targetAct->GetDamageHistoryCount();x > lastHistory; x--)
            {
                DamageHistory* history = targetAct->GetDamageHistory(x-1);
                if(history->attacker_ref->gemobj == attacker)
                {
                    dmgMade += history->damage;
                }
            }

            if (!totalDamage)
            {
                Error2("%s was found to have zero totalDamage in damagehistory!",targetAct->GetName() );
                continue;
            }
            // Use the latest HP (needs to be redesigned when NPCs can cast heal spells on eachoter)
            mod = dmgMade / totalDamage; // Get a 0.something value or 1 if we did all dmg
            if (mod > 1.0) 
                mod = 1.0;

            int final = int(exp * mod);
            
            psserver->SendSystemInfo(attacker->GetClientID(),"You gained %d experience points.",final);
            if (int pp = attacker->GetCharacterData()->AddExperiencePoints(final))
            {
                psserver->SendSystemInfo(attacker->GetClientID(),"You gained %d progression points.",pp);
            }
        }

        targetAct->ClearDamageHistory();

        return 0.0f;
    }

    bool Run(gemActor * actor, gemObject *target, ProgressionManager *mgr, bool inverse)
    {
        // Remove this when adding support for the inverse operation
        if (inverse)
            return true;

        int exp = (int)GetValue(actor,target,mgr);

        // allocate_dmg used to be in the if statement, but it should rightfully be the default
        // since we almost always want it and it is almost never in the data :)-- KWF
        if (type == "allocate_last")
        {
            if ( actor )
                actor->GetCharacterData()->AddExperiencePoints(exp);
        }
        else
        {
            result = AllocateKillDamage(target,mgr,exp);
        }
        return true;
    }
};

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

/**
 * Adjust the faction of the actor relative to the target.
 */
class FactionOp : public ProgressionOperation
{
protected:
    Faction *faction;
    // True if its the actors stat that should be used.
    bool aimIsActor;

public:
    FactionOp() : ProgressionOperation() { };
    virtual ~FactionOp() {};

    bool Load(iDocumentNode *node, ProgressionManager *mgr,ProgressionEvent *script)
    {
        if (node->GetAttributeValue("aim"))
        {
            aimIsActor = !strcasecmp(node->GetAttributeValue("aim"),"actor");
        }
        else
            aimIsActor = true; // Default

        faction = mgr->FindFaction(node->GetAttributeValue("name") );
        if (!faction)
        {
            Error2("Error: FactionOp faction(%s) not found\n",node->GetAttributeValue("name"));
            return false;
        }

        return LoadValue(node,mgr,script);
    }

    virtual csString ToString()
    {
        psString xml;
        xml.Format("<faction ");
        if (!aimIsActor)
            xml.AppendFmt("aim=\"target\" ");
        csString escpxml = EscpXML(faction->name);
        xml.AppendFmt("name=\"%s\" value=\"%s\" />",
                   escpxml.GetData(), script_text.GetData());
        return xml;
    }
    
    
    bool Run(gemActor * actor, gemObject *target, ProgressionManager *mgr, bool inverse)
    {
        // Remove this when adding support for the inverse operation
        if (inverse)
            return true;

        if (!aimIsActor && !target)
        {
            Error2("Error: ProgressionEvent(%s)  FactionOp need a target\n",eventName->GetData());
            return true;
        }

        if (aimIsActor && !actor)
        {
            Error2("Error: ProgressionEvent(%s)  FactionOp need an actor\n",eventName->GetData());
            return true;
        }
        
        psCharacter * character;
       
        if (aimIsActor)
            character = actor->GetCharacterData();
        else
            character = target->GetCharacterData();
        
        if (!character)
        {
            Error2("Error: ProgressionEvent(%s)  FactionOp aim %s isn't a character\n",eventName->GetData());
            return true;
        }

        int delta = (int)GetValue(actor,target,mgr);
        
        character->GetActor()->GetFactions()->UpdateFactionStanding(faction->id,delta);
        if (delta > 0)
        {
            psserver->SendSystemInfo(character->GetActor()->GetClientID(),"Your faction with %s has improved.",faction->name.GetData());
        } 
        else
        {
            psserver->SendSystemInfo(character->GetActor()->GetClientID(),"Your faction with %s has worsened.",faction->name.GetData());
        }
        return true;
    }
};

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

/**
 * Adjust the specified stat of the target.
 */
class StatsOp : public ProgressionOperation
{
public:
    
    typedef enum {HP,MANA,PSTAMINA,MSTAMINA,STR,AGI,END,INT,WIL,CHA,CON,STA,MSTA,ATTACK,DEFENSE,HPRATE} Stat_t;
    typedef enum {adjust_set, adjust_add, adjust_mul, adjust_pct} adjust_t;
    
    const static char * statToString[];
    const static PSITEMSTATS_STAT statToAttrib[];
    
    StatsOp(Stat_t state):ProgressionOperation() {stat=state;};
    virtual ~StatsOp() {};

    bool Load(iDocumentNode *node, ProgressionManager *mgr,ProgressionEvent *script)
    {
        adjust = adjust_add;
        if (node->GetAttributeValue("adjust"))
        {
            csString adjustStr = node->GetAttributeValue("adjust");
            if (adjustStr == "set")
                adjust = adjust_set;
            else if (adjustStr == "mul")
                adjust = adjust_mul;
            else if (adjustStr == "pct")
                adjust = adjust_pct;                
        }

        if (node->GetAttributeValue("aim"))
            aimIsActor = !strcasecmp(node->GetAttributeValue("aim"),"actor");
        else
            aimIsActor = true; // Default

        base = (node->GetAttributeValue("base") && !strcmp(node->GetAttributeValue("base"), "yes"));

        undoMsg = node->GetAttributeValue("undomsg");

        return LoadValue(node,mgr,script)  &&  LoadDelay(node,mgr,script);
    }
    
    virtual csString ToString()
    {
        psString xml;
        psString adjustStr, delayStr;
        
        xml.Format("<%s ",statToString[stat]);
        if (!aimIsActor)
            xml.AppendFmt("aim=\"target\" ");
        
        switch (adjust)
        {
            case adjust_set: adjustStr = "set"; break;
            case adjust_add: adjustStr = "add"; break;
            case adjust_mul: adjustStr = "mul"; break;            
            case adjust_pct: adjustStr = "pct"; break;
        }
        
        xml.AppendFmt("adjust=\"%s\" ", adjustStr.GetData());
        if (base)
            xml.AppendFmt("base=\"yes\" ");
        xml.AppendFmt("delay=\"%s\" ", delay_text.GetData());
        if (undoMsg.GetData() != NULL)
            xml.AppendFmt("undomsg=\"%s\" ", undoMsg.GetData());
        xml.AppendFmt("value=\"%s\" />",script_text.GetData() );

        return xml;
    }

    float GetCurrentValue(psCharacter * targetChar, bool findBase = false)
    {
        switch (stat)
        {
        case HP:
            if (base)
                return targetChar->GetHitPointsMax();
            else
                return targetChar->GetHP();
            break;
        case MANA:
            if (base)
                return targetChar->GetManaMax();
            else
                return targetChar->GetMana();
           break;
        case PSTAMINA:
            if (base)
                return targetChar->GetStaminaMax(true);
            else
                return targetChar->GetStamina(true);
            break;
        case MSTAMINA:
            if (base)
                return targetChar->GetStaminaMax(false);
            else
                return targetChar->GetStamina(false);
            break;
        case STR:        
        case AGI:
        case END:
        case INT:
        case WIL:
        case CHA:
        case CON:
        case STA:
            if (base || findBase)
                return targetChar->GetAttributes()->GetStat(statToAttrib[stat], false);
            else
                return targetChar->GetAttributes()->GetStat(statToAttrib[stat], true);
            break;
        case ATTACK:
            return targetChar->GetAttackValueModifier();
        case DEFENSE:
            return targetChar->GetDefenseValueModifier();
        case HPRATE:
            return targetChar->AdjustHitPointsRate(0.0);
        default:
            return 0.0;            
        }
        return 0.0;
    }
    
    float CalcNewValue(float oldValue, float adjustValue, bool inverse, float baseValue)
    {
        switch (adjust)
        {
            case adjust_set: return adjustValue;
            case adjust_add: if (inverse) return oldValue - adjustValue; else return oldValue + adjustValue; 
            case adjust_mul: if (inverse) return oldValue / adjustValue; else return oldValue * adjustValue;
            case adjust_pct:
            {
                float pct = baseValue * adjustValue/100.0f;
                if ( inverse )
                    return oldValue-pct;
                else    
                    return oldValue+pct;               
            }
        }
                        
        return 0.0;
    }
    
    void SetValue(gemActor * actor, psCharacter * targetChar, float oldValue, float newValue, int duration)
    {
        switch (stat)
        {
        case HP:
            if (base)
                targetChar->SetHitPointsMax(newValue);
            else
            {
                if (newValue >= oldValue)
                    targetChar->SetHitPoints(newValue);
                else
                    targetChar->GetActor()->DoDamage(actor, targetChar->GetHP()-newValue);
            }
            break;
        case MANA:
            if (base)
                targetChar->SetManaMax(newValue);
            else
                targetChar->SetMana(newValue);
           break;
        case PSTAMINA:
            if (base)
                targetChar->SetStaminaMax(newValue,true);
            else
                targetChar->SetStamina(newValue,true);
            break;
        case MSTAMINA:
            if (base)
                targetChar->SetStaminaMax(newValue,false);
            else
                targetChar->SetStamina(newValue,false);
            break;
        case STR:        
        case AGI:
        case END:
        case INT:
        case WIL:
        case CHA:
        case CON:
        case STA:
            if (base)
                targetChar->GetAttributes()->SetStat(statToAttrib[stat],(unsigned)newValue);
            else
                targetChar->GetAttributes()->BuffStat(statToAttrib[stat],unsigned(newValue-oldValue));
            break;
        case ATTACK:
            targetChar->AdjustAttackValueModifier(newValue/oldValue);
            break;
        case DEFENSE:
            targetChar->AdjustDefenseValueModifier(newValue/oldValue);
            break;
        case HPRATE:
            targetChar->GetActor()->DoDamage(actor, 0.0, newValue-oldValue, duration);
            break;
        default:
            break;            
        }
    
    // This function recalculates the target HP, Mana and Stamina
    targetChar->RecalculateStats();
    }
    
    csString CreateUndoScript(psCharacter * targetChar, float oldValue, float finalValue)
    {
        psString script;
        psString baseStr, statName, adjustStr, aimStr;
        float value;
        
        if (base)
            baseStr = "yes";
        else
            baseStr = "no";
        
        script = "<evt>";
        switch (stat)
        {
        case HP:
            statName = "hp";
            break;
        case MANA:
            statName = "mana";
           break;
        case PSTAMINA:
            statName = "pstamina";
            break;
        case MSTAMINA:
            statName = "mstamina";
            break;
        case STR:        
        case AGI:
        case END:
        case INT:
        case WIL:
        case CHA:
        case CON:
        case STA:
            statName = statToString[stat];
            break;
        case ATTACK:
            statName = "attack";
            break;
        case DEFENSE:
            statName = "defense";
            break;
        case HPRATE:
            statName = "hpRate";
            break;
        default:
            break;            
        }
        
        if (adjust == adjust_mul)
        {
            adjustStr = "mul";
            value = oldValue / finalValue;
        }
        else
        {
            adjustStr = "add";
            value = oldValue - finalValue;
        }
        
        script.AppendFmt("<%s adjust=\"%s\" aim=\"target\" base=\"%s\" value=\"%f\">", 
                            statName.GetData(), adjustStr.GetData(), baseStr.GetData(), value);
        script += "</" + statName + ">";
        if (undoMsg.Length() > 0)
            script.AppendFmt("<msg aim=\"target\" text=\"%s\"/>", undoMsg.GetData());
        script += "</evt>";
    
    // This function recalculates the target HP, Mana and Stamina
    targetChar->RecalculateStats();
        
        return script;
    }

    bool Run(gemActor * actor, gemObject *target, ProgressionManager *mgr, bool inverse)
    {
        psCharacter * targetChar;
        gemActor * object;
        // return 0.0f; // Disabled to stop crashes when target is an invalid pointer
        object = dynamic_cast <gemActor*> (aimIsActor ? actor : target);
        if (!object)
        {
            Error2("Error: ProgressionEvent(%s)  StatsOp need a target\n",eventName->GetData());
            return true;
        }
        
        targetChar = object->GetCharacterData();
        if (!targetChar)
        {
            Error3("Error: ProgressionEvent(%s)  StatsOp aim %s isn't a character\n",eventName->GetData(),object->GetName() );
            return true;
        }
        
	    Client* client = NULL;
	    if (actor)
	       client = psserver->GetConnections()->Find( actor->GetClientID() );
	       
        if (client != NULL)
            object->SendTargetStatDR( client );
        
        int delay = (int)GetDelay(actor,target,mgr);
        float oldValue = GetCurrentValue(targetChar);
        float adjustValue = GetValue(actor,target,mgr);
        float baseValue = GetCurrentValue( targetChar, true );
        float newValue = CalcNewValue(oldValue, adjustValue, inverse, baseValue);

        SetValue(actor, targetChar, oldValue, newValue, delay);

        if (delay != 0)                    
        {
            float finalValue = GetCurrentValue(targetChar);
            csString undoScript = CreateUndoScript(targetChar, oldValue, finalValue);
            
            mgr->QueueScript(undoScript.GetData(), delay, actor, object);
        }
        return true;
    }
    
protected:

    adjust_t adjust; 

    // True if it is the base value that should be used.
    bool base; 

    // True if its the actors stat that should be used.
    bool aimIsActor;
    
    csString undoMsg;
    
    Stat_t stat;
};

const char *StatsOp::statToString[] = {"hp","mana","pstamina","mstamina","str","agi","end","int","wil","cha","con","sta","msta", "attack", "defense", "hpRate"};
const PSITEMSTATS_STAT StatsOp::statToAttrib[] = 
    {PSITEMSTATS_STAT_NONE, // hp
     PSITEMSTATS_STAT_NONE, // mana
     PSITEMSTATS_STAT_NONE, //stamina
    PSITEMSTATS_STAT_NONE, //stamina
     PSITEMSTATS_STAT_STRENGTH,
     PSITEMSTATS_STAT_AGILITY,
     PSITEMSTATS_STAT_ENDURANCE,
     PSITEMSTATS_STAT_INTELLIGENCE,
     PSITEMSTATS_STAT_WILL,
     PSITEMSTATS_STAT_CHARISMA,
     PSITEMSTATS_STAT_CONSTITUTION,
     PSITEMSTATS_STAT_STAMINA,
     PSITEMSTATS_STAT_NONE, // attack modifier
     PSITEMSTATS_STAT_NONE, // defense modifier
     PSITEMSTATS_STAT_NONE  // hp rate
     };

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

/**
 * Adjust the specified skill level of the target.
 */
class SkillOp : public ProgressionOperation
{
public:
    
    SkillOp() : ProgressionOperation() { };
    virtual ~SkillOp() {};

    bool Load(iDocumentNode *node, ProgressionManager *mgr,ProgressionEvent *script)
    {
        if (node->GetAttributeValue("aim"))
        {
            aimIsActor = !strcasecmp(node->GetAttributeValue("aim"),"actor");
        }
        else
            aimIsActor = true; // Default

        if (!node->GetAttributeValue("name"))
        {
            Error1("Error: No name for skill\n");
            return false;
        }
        
        csString name = node->GetAttributeValue("name");
        skill = CacheManager::GetSingleton().ConvertSkillString(name);
        bufferValue = (node->GetAttributeValue("buffer") && !strcmp(node->GetAttributeValue("buffer"),"yes"));
        setValue = (node->GetAttributeValue("adjust") && !strcmp(node->GetAttributeValue("adjust"),"set"));

        return LoadValue(node,mgr,script) && LoadDelay(node,mgr,script);
    }
    
    bool CreateUndoScript( float adjustValue, psString& undoScript )
    {
        undoScript = "<evt>";
        psSkillInfo* info = CacheManager::GetSingleton().GetSkillByID(skill);
        
        if ( info )
        {
            csString escname = EscpXML( info->name );
            undoScript.AppendFmt("<skill name=\"%s\" ", escname.GetData() );
            if ( !aimIsActor )
                undoScript.Append("aim=\"target\" ");       
            if ( setValue )
                undoScript.Append("adjust=\"set\" " );     
            
            undoScript.AppendFmt("value=\"%f\" />", -adjustValue );                                                                                            
        }   
        else
        {
            return false;
        }                         
        undoScript.Append("</evt>");
        return true;
    }

        
    virtual csString ToString()
    {
        psString xml;
        psSkillInfo * info = CacheManager::GetSingleton().GetSkillByID(skill);

        if (info)
        {
            csString escpxml = EscpXML(info->name);
            xml.Format("<skill name=\"%s\"", escpxml.GetData());
            if (!aimIsActor)
                xml.AppendFmt("aim=\"target\" ");
            if (setValue)
                xml.AppendFmt("adjust=\"set\" ");
            xml.AppendFmt("value=\"%s\" />",script_text.GetData() );
        }
        else
        {
            xml.Format ("<skill name=\"dummy\" />");
        }

        return xml;
    }

    bool Run(gemActor * actor, gemObject *target, ProgressionManager *mgr, bool inverse)
    {
        // Remove this when adding support for the inverse operation
        if (inverse)
            return true;

        if (!aimIsActor && !target)
        {
            Error2("Error: ProgressionEvent(%s)  SkillOp need a target\n",eventName->GetData());
            return true;
        }

        if (aimIsActor && !actor)
        {
            Error2("Error: ProgressionEvent(%s)  SkillOp need an actor\n",eventName->GetData());
            return true;
        }
        
        psCharacter * character;

        if (aimIsActor)
            character = actor->GetCharacterData();
        else
            character = target->GetCharacterData();

        if (!character)
        {
            Error2("Error: ProgressionEvent(%s)  SkillOp target %s isn't a character\n",eventName->GetData());
            return true;
        }
        
        int adjustValue = (int)GetValue(actor,target,mgr);  
        gemActor * object = dynamic_cast <gemActor*> (aimIsActor ? actor : target);
       
        if ( !bufferValue )
        {       
            result = (unsigned int)(adjustValue + (int)(setValue?0:character->GetSkills()->GetSkillRank(skill, false)));            
            character->GetSkills()->SetSkillRank(skill,(int)result);
        }
        else            
        {
            character->GetSkills()->BuffSkillRank( skill, adjustValue );
            result = adjustValue;
        }

        int delay = (int)GetDelay(actor, target, mgr);
        if ( delay != 0 )
        {
            psString undoScript;
            CreateUndoScript( adjustValue, undoScript );
            
            mgr->QueueScript( undoScript.GetData(), delay, actor, object );
        }
        
        return true;
    }
protected:
    // True if the return from the expression shall be used to 
    // set the skill value. Otherwise it will just adjust it.
    bool setValue; 
    // True if its the actors is the aim that should be used.
    bool aimIsActor;
    
    // True if the skill should be buffed
    bool bufferValue;
    PSSKILL skill;
};

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

/**
 * Send a message to the target OR the actor.
 */
class MsgOp : public ProgressionOperation
{
public:
    MsgOp() : ProgressionOperation() { };
    virtual ~MsgOp() {};

    bool Load(iDocumentNode *node, ProgressionManager *mgr,ProgressionEvent *script)
    {
        my_script = script;
    
        if (node->GetAttributeValue("aim"))
        {
            aimIsActor = !strcasecmp(node->GetAttributeValue("aim"),"actor");
        }
        else
            aimIsActor = true; // Default

        text = node->GetAttributeValue("text");
        return true;
    }
    
    virtual csString ToString()
    {
        psString xml;
        xml.Format("<msg ");
        if (!aimIsActor)
            xml.AppendFmt("aim=\"target\" ");
    csString escpxml = EscpXML(text);
        xml.AppendFmt("text=\"%s\" />",escpxml.GetData());
        return xml;
    }

    bool Run(gemActor * actor, gemObject *target, ProgressionManager *mgr, bool inverse)
    {
        // Remove this when adding support for the inverse operation
        if (inverse)
            return true;

        if (!aimIsActor && !target)
        {
            Error2("Error: ProgressionEvent(%s) MsgOp need a target\n",eventName->GetData());
            return true;
        }

        if (aimIsActor && !actor)
        {
            Error2("Error: ProgressionEvent(%s)  MsgOp need an actor\n",eventName->GetData());
            return true;
        }

        int clientID;
        if (aimIsActor)
            clientID = actor->GetClientID();
        else
            clientID = target->GetClientID();
        

        if (!clientID)
        {
            // Spell on NPC instead of PC--not really an error in the script.
            // CPrintf(CON_ERROR, "Error: ProgressionEvent(%s)  MsgOp aim isn't connected to a client.",
            //        eventName->GetData());
            return true;
        }

        psString sendtext(text);
        SubstituteVars(sendtext);

        psserver->SendSystemInfo(clientID,sendtext);
               

        return true;
    }
protected:

    void SubstituteVars(psString& str)
    {
        int where = (int) str.FindFirst('$');
        while (where != -1)
        {
            psString word;
            str.GetWord(where+1,word,false);
            MathScriptVar *pv = my_script->FindVariable(word);
            if (pv)
            {
                csString buff;
                buff.Format("%1.0f",pv->GetValue() );
                str.ReplaceSubString(word,buff);
                str.DeleteAt(where);
            }
            where = (int) str.FindFirst('$',where+1);
        }
    }

    csString text;
    // True if its the actors is the aim that should be used.
    bool aimIsActor;
};

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

/**
 * Block a category of spells from being recast again for a
 * specified time delay.
 */
class BlockOp : public ProgressionOperation
{
public:
    typedef enum {BLOCK_ADD, BLOCK_REMOVE} Operation_t;

    BlockOp() : ProgressionOperation() { };
    virtual ~BlockOp() {};

    const static char * operationToString[];
    
    bool Load(iDocumentNode *node, ProgressionManager *mgr,ProgressionEvent *script)
    {
        my_script = script;
        if ( node->GetAttribute("operation") )
        {
            csString operationStr = node->GetAttributeValue("operation");
            if ( operationStr.CompareNoCase(operationToString[BLOCK_ADD]) )
            {
                operation = BLOCK_ADD;
            }
            else
            {
                operation = BLOCK_REMOVE;
            }
        }
        else
        {
            operation = BLOCK_ADD;
        }
        category = node->GetAttributeValue("category");
        return LoadDelay(node,mgr,script);;
    }
    
    virtual csString ToString()
    {
        psString xml;
    csString escpxml = EscpXML(operationToString[operation]);
        xml.Format( "<block " );
        xml.AppendFmt( "operation=\"%s\" ", escpxml.GetData() );
    escpxml = EscpXML(category);
        xml.AppendFmt( "category=\"%s\" ", escpxml.GetData() );
        xml.AppendFmt( "delay=\"%s\" ", delay_text.GetData() );
        xml.AppendFmt( " /> " );
        return xml;
    }

    bool Run(gemActor * actor, gemObject *target, ProgressionManager *mgr, bool inverse)
    {
        // Remove this when adding support for the inverse operation
        if (inverse)
            return true;

        gemActor *targetActor;

        if ( !target )
        {
            Error2("Error: ProgressionEvent(%s) BlockOp needs a target\n",eventName->GetData());
            return 0.0f;
        }

        targetActor = target->GetActorPtr();
        switch ( operation )
        {
        case BLOCK_ADD:

            if ( ( targetActor->AddSpellCategory( category ) != -1 ) )
            {
            int delay = (int)GetDelay(actor,target,mgr);
            
                if (delay != 0)
                {            
                    psString undoscript = CreateUndoScript();
                    mgr->QueueScript(undoscript.GetData(), delay, actor, target);
                }
            }
            else
            {
                return false;
            }
            break;

        case BLOCK_REMOVE:
            targetActor->RemoveSpellCategory( category );
            break;
        }

        return true;
    }

protected:

    psString CreateUndoScript( void )
    {
        psString script;
        
        script = "<evt>";
        script.Append("<block ");
        csString escpxml = EscpXML(operationToString[BLOCK_REMOVE]);
        script.AppendFmt( "operation=\"%s\" ", escpxml.GetData() );
        escpxml = EscpXML(category);
        script.AppendFmt( "category=\"%s\" ", escpxml.GetData() );
        script.AppendFmt( "delay=\"%s\" ", delay_text.GetData() );
        script.Append(" /> ");
        script.Append("</evt>");

        return script;
    }

    Operation_t operation;
    csString category;
};
const char *BlockOp::operationToString[] = {"add","remove"};

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

/**
 * Attach scripts reacting on gemActor events to a gemActor
 */
class AttachScriptOp : public ProgressionOperation
{
public:
    AttachScriptOp() : ProgressionOperation() { };
    virtual ~AttachScriptOp() {};

    bool Load(iDocumentNode *node, ProgressionManager *mgr,ProgressionEvent *script)
    {
        my_script = script;
        
        if (node->GetAttributeValue("aim"))
            aimIsActor = !strcasecmp(node->GetAttributeValue("aim"),"actor");
        else
            aimIsActor = true; // Default

        scriptName = node->GetAttributeValue("scriptName");
        event = node->GetAttributeValue("event");
        undoMsg = node->GetAttributeValue("undomsg");
        return LoadDelay(node,mgr,script);
    }
    
    virtual csString ToString()
    {
        psString xml;
        xml.Format("<attachscript ");
        if (!aimIsActor)
            xml.AppendFmt("aim=\"target\" ");
        xml.AppendFmt("delay=\"%s\" ", delay_text.GetData());
        xml.AppendFmt("scriptName=\"%s\" ", scriptName.GetData());
        xml.AppendFmt("event=\"%s\" ", event.GetData());
        xml.AppendFmt("undomsg=\"%s\" ", undoMsg.GetData());
        xml += "/>";
        return xml;
    }

    bool Run(gemActor * actor, gemObject *target, ProgressionManager *mgr, bool inverse)
    {
        // Remove this when adding support for the inverse operation
        if (inverse)
            return true;

        gemActor * object;
        int scriptID;
        
        if (aimIsActor)
            object = dynamic_cast<gemActor*> (actor);
        else
            object = dynamic_cast<gemActor*> (target);
        
        if (!object)
        {
            Error2("Error: ProgressionEvent(%s) DetachScriptOp need a target\n",eventName->GetData());
            return true;
        }
        
        if (event == "attack")
            scriptID = object->AttachAttackScript(scriptName);
        else
            scriptID = object->AttachDamageScript(scriptName);
        
        int delay = (int)GetDelay(actor,target,mgr);
        
        if (delay != 0)
        {
            psString undoScript;
            undoScript.AppendFmt("<evt><detachscript aim=\"target\" event=\"%s\" scriptID=\"%i\"/> ", event.GetData(), scriptID);
            if (undoMsg.Length() > 0)
                undoScript.AppendFmt("<msg aim=\"target\" text=\"%s\"/>", undoMsg.GetData());
            undoScript += "</evt>";
            mgr->QueueScript(undoScript.GetData(), delay, actor, object);
        }
        
        return true;
    }
protected:

    bool aimIsActor;
    csString event;
    csString scriptName;
    csString undoMsg;
};

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

/**
 * Detach scripts reacting on gemActor events from a gemActor
 */
class DetachScriptOp : public ProgressionOperation
{
public:
    DetachScriptOp() : ProgressionOperation() { };
    virtual ~DetachScriptOp() {};

    bool Load(iDocumentNode *node, ProgressionManager *mgr,ProgressionEvent *script)
    {
        my_script = script;
    
        if (node->GetAttributeValue("aim"))
            aimIsActor = !strcasecmp(node->GetAttributeValue("aim"),"actor");
        else
            aimIsActor = true; // Default
            
        scriptID = node->GetAttributeValueAsInt("scriptID");
        event = node->GetAttributeValue("event");
        return true;
    }
    
    virtual csString ToString()
    {
        psString xml;
        xml.Format("<detachscript ");
        if (!aimIsActor)
            xml.AppendFmt("aim=\"target\" ");
        xml.AppendFmt("scriptID=\"%i\" ", scriptID);
        xml.AppendFmt("event=\"%s\" ", event.GetData());
        xml += "/>";
        return xml;
    }

    bool Run(gemActor * actor, gemObject *target, ProgressionManager *mgr, bool inverse)
    {
        // Remove this when adding support for the inverse operation
        if (inverse)
            return true;

        gemActor * object;
        
        if (aimIsActor)
            object = dynamic_cast<gemActor*> (actor);
        else
            object = dynamic_cast<gemActor*> (target);
            
        if (!object)
        {
            Error2("Error: ProgressionEvent(%s) DetachScriptOp need a target\n",eventName->GetData());
            return 0.0;
        }
        
        if (event == "attack")
            object->DetachAttackScript(scriptID);
        else
            object->DetachDamageScript(scriptID);
        
        return true;
    }
protected:

    bool aimIsActor;
    csString event;
    int scriptID;
};

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

/**
 * Determine if 'target' is magical item and tell 'actor' the outcome
 */
class IdentifyMagicOp : public ProgressionOperation
{
public:
    IdentifyMagicOp() : ProgressionOperation() { };
    virtual ~IdentifyMagicOp() {};

    bool Load(iDocumentNode *node, ProgressionManager *mgr,ProgressionEvent *script)
    {
        return true;
    }
    
    virtual csString ToString()
    {
        psString xml;
        xml.Format("<identifymagic");
        xml += "/>";
        return xml;
    }

    bool Run(gemActor * actor, gemObject *target, ProgressionManager *mgr, bool inverse)
    {
        // Remove this when adding support for the inverse operation
        if (inverse)
            return true;

        gemItem * item;
        int clientnum;
        
        if (!actor)
        {
            Error2("Error: ProgressionEvent(%s) IdentifyMagicOp needs an actor\n",eventName->GetData());
            return true;
        }

        clientnum = actor->GetClientID();
        if (clientnum == 0)
        {
            Error2("Error: ProgressionEvent(%s) IdentifyMagicOp needs a client\n",eventName->GetData());
            return true;
        }
        
        item = dynamic_cast <gemItem*> (target);
        if (!item)
        {
            psserver->SendSystemError(clientnum,"You must have an item selected");
            return true;
        }
        
        psItemStats * stats = item->GetItem()->GetBaseStats();
        // this is really bad.. should have specific flag?
        if (!stats->GetIsConsumable() && (stats->GetProgressionEventEquip().Length()>0 || stats->GetProgressionEventUnEquip().Length()>0))
            psserver->SendSystemInfo(clientnum,"You found magical properties in this item !");
        else
            psserver->SendSystemInfo(clientnum,"This is an ordinary item without any magical powers.");
        return true;
    }
};

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

/**
 * Queue another named script to run after a specifed delay.
 */
class ScriptOp : public ProgressionOperation
{
public:
    ScriptOp() : ProgressionOperation() { };
    virtual ~ScriptOp(){};
    
    bool Load(iDocumentNode *node, ProgressionManager *mgr,ProgressionEvent *prg_script)
    {
        delay = node->GetAttributeValueAsInt("delay");
        if (node->GetAttributeValue("persistent"))
        {
            persistent = !strcmp(node->GetAttributeValue("persistent"),"yes");
        }
        else persistent = false;
        script.name = *eventName;
        script.LoadScript(node,mgr);
        return true;
    }
    
    virtual csString ToString()
    {
        csString xml;
        csString script_str = script.ToString(false);
        xml.Format("<script delay=\"%d\" %s/>%s</script>",
                   delay,(persistent?"persistent=\"yes\" ":""),
                   script_str.GetData());
        return xml;
    }

    bool Run(gemActor * actor, gemObject *target, ProgressionManager *mgr, bool inverse)
    {
        // Remove this when adding support for the inverse operation
        if (inverse)
            return true;

        mgr->QueueEvent( new psScriptGameEvent(delay,mgr,&script,actor,target,persistent) );

        return true;
    }

protected:
    int delay;
    bool persistent;
    ProgressionEvent script;
};

/*-------------------------------------------------------------*/
/* Applies a script to everything of a type in an area.        */
/*  Syntax:                                                    */
/*    <area type="entity|item|actor|group|hostile|friendly"    */
/*          range="#"                                          */
/*          anglerange="#"    (in degrees)                     */
/*          delaybetween="#"  (in ms; default all at once)     */
/*          includetarget="yes|no" >  (default yes)            */
/*    script                                                   */
/*    </area>                                                  */

class AreaOp : public ProgressionOperation
{
public:
    AreaOp() : ProgressionOperation() { };
    virtual ~AreaOp(){};
    
    enum target_type { ENTITY, ITEM, ACTOR, GROUP, HOSTILE, FRIENDLY };

    bool Load(iDocumentNode *node, ProgressionManager *mgr,ProgressionEvent *prg_script)
    {
        type = node->GetAttributeValue("type");
        range = node->GetAttributeValueAsInt("range");
        anglerange = node->GetAttributeValueAsInt("anglerange");
        delaybetween = node->GetAttributeValueAsInt("delaybetween");
        includetarget = node->GetAttributeValueAsBool("includetarget",true);

        if (type.IsEmpty())
        {
            Error2("ProgressionEvent(%s) AreaOp must specify an entity type\n",eventName->GetData() );
            return true;
        }
        else if (type == "entity")
            typecode = ENTITY;
        else if (type == "item")
            typecode = ITEM;
        else if (type == "actor")
            typecode = ACTOR;
        else if (type == "group")
            typecode = GROUP;
        else if (type == "hostile")
            typecode = HOSTILE;
        else if (type == "friendly")
            typecode = FRIENDLY;
        else
        {
            Error3("Invalid type in ProgressionEvent(%s) AreaOp: %s\n",eventName->GetData(), type.GetData() );
            return true;
        }
        
        if (range < 1 || range > 100)
        {
            Error2("Range in ProgressionEvent(%s) AreaOp must be at least 1 and less than 100\n",eventName->GetData() );
            return true;
        }

        if (anglerange < 0 || anglerange >= 360)
        {
            Error2("Angle range in ProgressionEvent(%s) AreaOp must be between 0 and 360\n",eventName->GetData() );
            return true;
        }

        if (delaybetween > 5000)
        {
            Error2("Delay between applies in ProgressionEvent(%s) AreaOp must be between 0 and 5000ms\n",eventName->GetData() );
            return true;
        }

        script.name = *eventName;
        script.LoadScript(node,mgr);
        return true;
    }
    
    virtual csString ToString()
    {
        csString xml;
        csString script_str = script.ToString(false);
        xml.Format("<area ");
        xml.AppendFmt("type=\"%s\" ", type.GetData() );
        xml.AppendFmt("range=\"%d\" ", range );
        if (anglerange) xml.AppendFmt("anglerange=\"%d\" ", anglerange );
        if (delaybetween) xml.AppendFmt("delaybetween=\"%u\" ", delaybetween );
        if (!includetarget) xml.AppendFmt("includetarget=\"no\" ");
        xml.Append(">");
        xml.Append(script_str);
        xml.Append("</area>");
        return xml;
    }

    bool Run(gemActor * actor, gemObject *target, ProgressionManager *mgr, bool inverse)
    {
        if (inverse)  // No inverse
            return true;

        if (!target)
        {
            // If we don't have a target, use the actor
            target = actor;

            if (!target)
            {
                Error2("Error: ProgressionEvent(%s) AreaOp needs a target or actor\n",eventName->GetData());
                return true;
            }
        }

        Client* client;
        if (anglerange || typecode > ACTOR)  /// anglerange, GROUP, HOSTILE, and FRIENDLY need info about the actor
        {
            if (!actor)
            {
                Error2("Error: ProgressionEvent(%s) AreaOp needs an actor\n",eventName->GetData());
                return true;
            }

            if (typecode > GROUP)  /// HOSTILE and FRIENDLY need info about the actor's client
            {
                client = actor->GetClient();
                if (!client)
                {
                    Error2("Error: ProgressionEvent(%s) AreaOp actor needs a client\n",eventName->GetData());
                    return true;
                }
            }
        }

        csVector3 actor_pos;    /// Actor's position
        csVector3 target_pos;   /// Target's position
        iSector* actor_sector;  /// Actor's sector
        iSector* target_sector; /// Target's sector

#define NORMALIZE_BIG_ANGLE(a)  { if (a > TWO_PI) a -= TWO_PI; }
#define NORMALIZE_NEG_ANGLE(a)  { if (a < 0.0f) a += TWO_PI; }

        float max_angle, min_angle;
        if (anglerange)
        {
            float actor_angle;
            actor->GetPosition(actor_pos,actor_angle,actor_sector);
            
            // angle is actually in the opposite direction the character is facing...
            actor_angle += PI;
            NORMALIZE_BIG_ANGLE(actor_angle);

            /// We get 1/2 of the given range to either side of the actor's facing angle.
            float half_range = float(anglerange) * ((PI/180.0f) * 0.5f);  // In radians
            max_angle = actor_angle + half_range;
            min_angle = actor_angle - half_range;
            NORMALIZE_BIG_ANGLE(max_angle);
            NORMALIZE_NEG_ANGLE(min_angle);
        }

        if (anglerange && target == actor)
        {   
            // Just copy the values if we already have them
            target_pos = actor_pos;
            target_sector = actor_sector;
        }
        else
        {
            target->GetPosition(target_pos,target_sector);
        }

        static GEMSupervisor* gem = psserver->GetEntityManager()->GetGEM();
        static psWorld* world = psserver->GetEntityManager()->GetWorld();

        csRef<iCelEntityList> nearlist = gem->pl->FindNearbyEntities(target_sector,target_pos,range);
        size_t count = nearlist->GetCount();
        for (size_t i=0, n=0; i<count; i++)
        {
            gemObject* nearobj = gem->GetObjectFromEntityList(nearlist,i);

            if (!includetarget && nearobj == target)
                continue;

            switch (typecode)
            {
                case ENTITY:
                    break;  // Everything

                case ITEM:
                    if ( nearobj->GetItem() )
                        break; else continue;

                case ACTOR:
                    if ( nearobj->GetPlayerID() )
                        break; else continue;

                case GROUP:
                    if ( actor->IsGroupedWith(nearobj->GetActorPtr()) )
                        break; else continue;

                case HOSTILE:
                    if ( client->IsAllowedToAttack(nearobj,false) )
                        break; else continue;

                case FRIENDLY:
                    if ( !client->IsAllowedToAttack(nearobj,false) )
                        break; else continue;
            }

            if (anglerange && nearobj != target)
            {
                nearobj->GetPosition(target_pos,target_sector);
                world->WarpSpace(target_sector,actor_sector,target_pos);

                float dx = target_pos.x - actor_pos.x;
                float dz = target_pos.z - actor_pos.z;
                float angle = atan2f(dx,dz);
                NORMALIZE_NEG_ANGLE(angle);

                if (max_angle > min_angle)
                {
                    if (angle > max_angle || angle < min_angle)
                        continue;
                }
                else  // Wedge includes 0
                {
                    if (angle > max_angle && angle < min_angle)
                        continue;
                }
            }

            // Queue an event for each intended object
            mgr->QueueEvent( new psScriptGameEvent(delaybetween*n++, mgr, &script, actor, nearobj, true) );
        }

        return true;
    }

protected:
    int range;                /// Range of area application, in meters
    int anglerange;           /// Wedge of area to apply to, in degrees
    csString type;            /// Type of entity to apply to
    target_type typecode;     /// Code for type, from enum
    csTicks delaybetween;     /// Delay between each apply, in miliseconds
    bool includetarget;       /// Apply to target, or just area around?
    ProgressionEvent script;  /// Script to apply to each
};

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

class ItemOp : public ProgressionOperation
{
public:
    ItemOp() : ProgressionOperation() { };
    virtual ~ItemOp() {};

    bool Load(iDocumentNode *node, ProgressionManager *mgr,ProgressionEvent *script)
    {
        if (node->GetAttributeValue("aim"))
        {
            aimIsActor = !strcasecmp(node->GetAttributeValue("aim"),"actor");
        }
        else
            aimIsActor = true; // Default

        name = node->GetAttributeValue("name");
        location = node->GetAttributeValue("location");
        stackCount = node->GetAttributeValueAsInt("count");
        if (location != "" && location != "inventory" && 
            location != "wallet" && location != "ground" )
        {
            Error3("Error:ProgressionEvent(%s) ItemOp Location %s not legal\n",eventName->GetData(), location.GetData());
        }
        return true;
    }
    
    virtual csString ToString()
    {
        psString xml;
    csString escpxml = EscpXML(name);
        xml.Format("<item name=\"%s\" ",escpxml.GetData());
        if (location != "") 
    {
        escpxml = EscpXML(location);
            xml.AppendFmt("location=\"%s\" ",escpxml.GetData());
    }
        if (!aimIsActor)
            xml.AppendFmt("aim=\"target\" ");
        if (stackCount != 0) 
            xml.AppendFmt("count=\"%d\" ",stackCount);
        xml.AppendFmt("/>");
        return xml;
    }

    bool Run(gemActor * actor, gemObject *target, ProgressionManager *mgr, bool inverse)
    {    
        // Remove this when adding support for the inverse operation
        if (inverse)
            return true;

        if (!aimIsActor && !target)
        {
            Error2("Error: ProgressionEvent(%s) ItemOp need a target\n",eventName->GetData());
            return true;
        }

        if (aimIsActor && !actor)
        {
            Error2("Error: ProgressionEvent(%s)  ItemOp need an actor\n",
                    eventName->GetData());
            return true;
        }

        psCharacter * character;
       
        if (aimIsActor)
            character = actor->GetCharacterData();
        else
            character = target->GetCharacterData();

        if (!character)
        {
            Error3("Error: ProgressionEvent(%s) ItemOp No character data was found for target %s.\n",
                    eventName->GetData(),(aimIsActor?actor->GetName():target->GetName()));
            return true;
        }

        //This is for a player that is given some money
        if ( location == "wallet" )
        {
            psMoney money;
            
            if ( name == "trias" )
                money.SetTrias( stackCount );
            if ( name == "hexas" )
                money.SetHexas( stackCount );
            if ( name == "octas" )
                money.SetOctas( stackCount );
            if ( name == "circles" )
                money.SetCircles( stackCount );
                                           
            psMoney charMoney = character->Money(); 
            charMoney = charMoney + money;
            character->SetMoney( charMoney );
            return true;
        } 
        else if (location == "inventory")
        {
            psItem * iteminstance = CreateItem(false);
            if (!iteminstance)
            {
                return true;
            }

            if ( !character->Inventory().PutInBulk(iteminstance) )
            {
                Error3("Error: ProgressionEvent(%s) ItemOp The item %s did not fit into the character's inventory.\n",
                        eventName->GetData(),iteminstance->GetName());
                CacheManager::GetSingleton().RemoveInstance(iteminstance);
                return true;
            }
        } 
        else if (location == "ground")
        {
            psItem * iteminstance = CreateItem(true);
            if (!iteminstance)
            {
                return true;
            }

            psSectorInfo *sectorinfo;
            float loc_x,loc_y,loc_z,loc_yrot;

            character->GetLocationInWorld(sectorinfo,loc_x,loc_y,loc_z,loc_yrot);
            iteminstance->SetLocationInWorld(sectorinfo,loc_x,loc_y,loc_z,loc_yrot);

            if (!psserver->GetEntityManager()->CreateItem(iteminstance,true))
            {
                delete iteminstance;
            }
        }
        return true;
    }

    psItem *CreateItem(bool transient)
    {
            
        // Get the ItemStats based on the name provided.
        psItemStats *itemstats=CacheManager::GetSingleton().GetBasicItemStatsByName(name.GetData());
        if (!itemstats)
        {
            Error3("Error: ProgressionEvent(%s) ItemOp No Basic Item Template with name %s was found.\n",
                    eventName->GetData(),name.GetData());
            return NULL;
        }
        
        psItem *iteminstance = itemstats->InstantiateBasicItem(transient);
        if (iteminstance==NULL)
        {
            Error2("Error: ProgressionEvent(%s) ItemOp Could not instanciate item based on basic properties.\n",
                    eventName->GetData());
            return NULL;
        }
       
        if (stackCount != 0)
        {
            if (!iteminstance->GetIsStackable())
            {
                Error2("Error: ProgressionEvent(%s) ItemOp Item isn't statckable.\n",eventName->GetData());
            }
            else
            {
                iteminstance->SetStackCount(stackCount);
            }
        }
        
        iteminstance->SetLoaded();  // Item is fully created
        iteminstance->Save();    // First save

        return iteminstance;
    }
    
protected:
    csString name;
    csString location;
    int stackCount;
    // True if its the actors is the aim that should be used.
    bool aimIsActor;
};

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

class PurifyOp : public ProgressionOperation
{
public:
    PurifyOp() : ProgressionOperation() { };
    virtual ~PurifyOp() {};

    bool Load(iDocumentNode *node, ProgressionManager *mgr,ProgressionEvent *script)
    {
        glyphUID = node->GetAttributeValueAsInt("glyph");
        return true;
    }
    
    virtual csString ToString()
    {
        csString xml ;
        xml.Format("<purify glyph=\"%u\" />",glyphUID);
        return xml;
    }

    bool Run(gemActor * actor, gemObject *target, ProgressionManager *mgr, bool inverse)
    {
        // Remove this when adding support for the inverse operation
        if (inverse)
            return true;

        if (!actor)
        {
            Error2("Error: ProgressionEvent(%s) PurifyOp need an actor\n",eventName->GetData());
            return true;
        }

        psCharacter *character = actor->GetCharacterData();
        
        if (!character)
        {
            Error2("Error: ProgressionEvent(%s) PurifyOp need a character\n",eventName->GetData());
            return true;
        }

        psserver->GetSpellManager()->EndPurifying(character,glyphUID);

        return true;
    }
protected:
    uint32 glyphUID;
};

/*-------------------------------------------------------------*/
/* Syntax:                                                     */
/*                <morph mesh="" duration="#" />               */
/*                (set mesh to "reset" to reset)               */

class MorphOp : public ProgressionOperation
{
public:
    MorphOp() : ProgressionOperation() { };
    virtual ~MorphOp() {};

    bool Load(iDocumentNode *node, ProgressionManager *mgr,ProgressionEvent *script)
    {
        mesh = node->GetAttributeValue("mesh");
        duration = node->GetAttributeValueAsInt("duration");
        return true;
    }

    virtual csString ToString()
    {
        csString xml;
        xml.Format("<morph mesh=\"%s\" duration=\"%d\" />", mesh.GetData(), duration );
        return xml;
    }

    bool Run(gemActor * actor, gemObject *target, ProgressionManager *mgr, bool inverse)
    {
        if (!actor)
        {
            Error2("Error: ProgressionEvent(%s) MorphOp need an actor\n",eventName->GetData());
            return true;
        }

        if (inverse || mesh == "reset")
            actor->ResetMesh();
        else
            actor->SetMesh(mesh);

        if (duration != 0)
        {
            // Queue undo script
            mgr->QueueScript("<evt><morph mesh=\"reset\" /></evt>",duration*1000,actor,actor);
        }

        return true;
    }

protected:
    csString mesh;  /// Mesh to morph into
    int duration;   /// Duration of effect in seconds
};

/*-------------------------------------------------------------*/
/* Syntax:                                                     */
/*  <set attrib="invincible|invisible|nofalldamage|nevertired" */
/*       value="true|false" duration="#" />  (default true)    */

class AttributeOp : public ProgressionOperation
{
public:
    AttributeOp() : ProgressionOperation() { };
    virtual ~AttributeOp() {};

    bool Load(iDocumentNode *node, ProgressionManager *mgr,ProgressionEvent *script)
    {
        attrib = node->GetAttributeValue("attrib");
        duration = node->GetAttributeValueAsInt("duration");
        value = node->GetAttributeValueAsBool("value",true);
        
        if (attrib != "invincible" && attrib != "invisible" && attrib != "nofalldamage" && attrib != "nevertired")
        {
            Error3("Invalid attribute for ProgressionEvent(%s) AttributeOp: %s\n", eventName->GetData(), attrib.GetData() );
            return true;
        }

        if (duration != 0)
        {
            // Create undo script
            undo.Format("<evt><set attrib=\"%s\" value=\"%s\" /></evt>", attrib.GetData(), (!value)?"true":"false" );
        }
        return true;
    }

    virtual csString ToString()
    {
        csString xml;
        xml.Format("<set attrib=\"%s\" value=\"%s\" duration=\"%d\" />", attrib.GetData(), (value)?"true":"false", duration );
        return xml;
    }

    bool Run(gemActor * actor, gemObject *target, ProgressionManager *mgr, bool inverse)
    {
        if (!actor)
        {
            Error2("Error: ProgressionEvent(%s) AttributeOp need an actor\n",eventName->GetData());
            return true;
        }

        bool setvalue = false;
        if (attrib == "invincible")
        {
            setvalue = (actor->GetInvincibility() != value);
            if (setvalue)
                actor->SetInvincibility(value);
        }
        else if (attrib == "invisible")
        {
            setvalue = (actor->GetVisibility() != !value);
            if (setvalue)
                actor->SetVisibility(!value);
        }
        else if (attrib == "nofalldamage")
        {
            setvalue = (actor->safefall != value);
            if (setvalue)
                actor->safefall = value;
        }
        else if (attrib == "nevertired")
        {
            setvalue = (actor->nevertired != value);
            if (setvalue)
                actor->nevertired = value;
        }

        if (duration != 0 && setvalue)
        {
            // Queue undo script
            mgr->QueueScript(undo,duration*1000,actor,actor);
        }

        return true;
    }

protected:
    csString undo;    /// Undo script
    csString attrib;  /// Attribute we're setting
    bool value;       /// Value we're setting to (true=on, false=off)
    int duration;     /// Duration of effect in seconds
};

/*-------------------------------------------------------------*/
/* Syntax:                                                     */
/*         <weather type="fog|rain|snow|lightning|auto"        */
/*                  sector=""  (may list "this" for target's)  */
/*                  duration="#" density="#" fade="#" (seconds)*/
/*                  r="#" g="#" b="#"  (fog only)              */
/*                  enable="true|false" />  (true by default)  */

class WeatherOp : public ProgressionOperation
{
public:
    WeatherOp() : ProgressionOperation() { };
    virtual ~WeatherOp() {};

    bool Load(iDocumentNode *node, ProgressionManager *mgr,ProgressionEvent *script)
    {
        type = node->GetAttributeValue("type");
        duration = node->GetAttributeValueAsInt("duration");
        density = node->GetAttributeValueAsInt("density");
        fade = node->GetAttributeValueAsInt("fade");
        r = node->GetAttributeValueAsInt("r");
        g = node->GetAttributeValueAsInt("g");
        b = node->GetAttributeValueAsInt("b");
        enable = node->GetAttributeValueAsBool("enable",true);

        // Convert seconds to miliseconds for WeatherManager
        duration *= 1000;
        fade *= 1000;

        csString sector = node->GetAttributeValue("sector");
        if (sector.IsEmpty())
        {
            Error2("Error: ProgressionEvent(%s) WeatherOp needs a sector name or \"this\"\n",eventName->GetData() );
            return true;
        }
        else if (sector == "this")
        {
            useCurLoc = true;
            sectorinfo = NULL;
        }
        else
        {
            useCurLoc = false;
            sectorinfo = CacheManager::GetSingleton().GetSectorInfoByName(sector);
            if (!sectorinfo)
            {
                Error3("Invalid sector for ProgressionEvent(%s) WeatherOp: %s\n",eventName->GetData(), sector.GetData() );
                return true;
            }
        }

        if (!enable && (duration || density || fade || r || g || b))
        {
            Error2("ProgressionEvent(%s) WeatherOp may only have \"type\" and \"sector\" with enable=\"false\"\n",eventName->GetData() );
            return true;
        }

        if (type == "fog")
            wtype = psWeatherMessage::FOG;
        else if (type == "rain")
            wtype = psWeatherMessage::RAIN;
        else if (type == "snow")
            wtype = psWeatherMessage::SNOW;
        else if (type == "lightning")
            wtype = psWeatherMessage::LIGHTNING;
        else if (type == "auto")
            wtype = 0;
        else
            wtype = -1;
            
        // Check parameters
        switch (wtype)
        {
            case psWeatherMessage::FOG:
            {
                break;
            }

            case psWeatherMessage::RAIN:
            {
                if (density < 0 || density > WEATHER_MAX_RAIN_DROPS)
                {
                    Error3("ProgressionEvent(%s) WeatherOp: Rain drop density must be between 0 and %d\n",
                           eventName->GetData(), WEATHER_MAX_RAIN_DROPS );
                    return true;
                }
                else break;
            }

            case psWeatherMessage::SNOW:
            {
                if (density < 0 || density > WEATHER_MAX_SNOW_FALKES)
                {
                    Error3("ProgressionEvent(%s) WeatherOp: Snow flake density must be between 0 and %d\n",
                            eventName->GetData(), WEATHER_MAX_SNOW_FALKES );
                    return true;
                }
                else break;
            }

            case psWeatherMessage::LIGHTNING:
            {
                if (sectorinfo && sectorinfo->lightning_max_gap == 0)
                {
                    Error3("Lightning undefined for sector in ProgressionEvent(%s) WeatherOp: %s\n",
                           eventName->GetData(), sector.GetData() );
                    return true;
                }
                else break;
            }

            case 0:  // "auto"
            {
                if (duration || density || fade || r || g || b)
                {
                    Error2("ProgressionEvent(%s) WeatherOp may only have \"sector\" and \"enable\" with type=\"auto\"\n",
                           eventName->GetData() );
                    return true;
                }
                else break;
            }
            
            default:
            {
                Error3("Invalid type for ProgressionEvent(%s) WeatherOp: %s\n",
                       eventName->GetData(), type.GetData() );
                return true;
            }
        }

        return true;
    }

    virtual csString ToString()
    {
        csString xml;
        xml.Format("<weather type=\"%s\" ", type.GetData() );
        xml.AppendFmt("sector=\"%s\" ", useCurLoc ? "this" : sectorinfo->name.GetData() );
        if (duration) xml.AppendFmt("duration=\"%d\" ", duration );
        if (density) xml.AppendFmt("density=\"%d\" ", density );
        if (fade) xml.AppendFmt("fade=\"%d\" ", fade );
        if (r) xml.AppendFmt("r=\"%d\" ", r );
        if (g) xml.AppendFmt("g=\"%d\" ", g );
        if (b) xml.AppendFmt("b=\"%d\" ", b );
        if (!enable) xml.Append("enable=\"false\" ");
        xml.Append("/>");
        return xml;
    }

    bool Run(gemActor * actor, gemObject *target, ProgressionManager *mgr, bool inverse)
    {
        if (inverse)
            enable = !enable;

        if (useCurLoc)
        {
            if (!target)
            {
                // If we don't have a target, use the actor
                target = actor;

                if (!target)
                {
                    Error2("Error: ProgressionEvent(%s) WeatherOp need a target or actor for sector=\"this\"\n",
                           eventName->GetData() );
                    return true;
                }
            }
            
            iSector* sector = target->GetSector();
            if (sector == NULL)
            {
                Error2("Error: ProgressionEvent(%s) WeatherOp target is in an invalid sector!\n",
                       eventName->GetData() );
                return true;
            }

            const char* sectorname = sector->QueryObject()->GetName();
            sectorinfo = CacheManager::GetSingleton().GetSectorInfoByName(sectorname);
            if (!sectorinfo)
            {
                Error3("Missing SectorInfo in ProgressionEvent(%s) WeatherOp: %s\n",
                       eventName->GetData(), sectorname );
                return true;
            }

            if (wtype == psWeatherMessage::LIGHTNING && sectorinfo->lightning_max_gap == 0)
            {
                Error3("Lightning undefined for sector in ProgressionEvent(%s) WeatherOp: %s\n",
                       eventName->GetData(), sectorname );
                return true;
            }
        }
        
        if (wtype)
        {
            if (enable)  // Queue the weather event
                psserver->GetWeatherManager()->QueueNextEvent(0,wtype,density,duration,fade,sectorinfo->name,sectorinfo,0,r,g,b);
            else
                psserver->GetWeatherManager()->QueueNextEvent(0,wtype,0,0,fade,sectorinfo->name,sectorinfo);
        }
        else  // "auto"
        {
            if (enable)
            {
                sectorinfo->rain_enabled = true;
                psserver->GetWeatherManager()->StartWeather(sectorinfo);
            }
            else
            {
                sectorinfo->rain_enabled = false;
                // Prevents new auto-weather, but does not need to stop anything in-progress
            }
        }

        return true;
    }

protected:
    psSectorInfo* sectorinfo;   /// Sector to work in
    bool useCurLoc;             /// Use target or actor's current sector
    csString type;              /// Type of weather
    int wtype;                  /// Type of weather code
    int duration;               /// Duration in seconds
    int density;                /// Density of fog/precipitation
    int fade;                   /// Fade in/out time in seconds
    int r, g, b;                /// Color for fog
    bool enable;                /// Are we turning on or off?
};

/*-------------------------------------------------------------*/
/* Syntax:                                                       */
/*            <createpet masterids="##,##,##" 
                       controlled="true|false" />              */

class CreatePetOp : public ProgressionOperation
{
public:

    virtual bool Run(gemActor * actor, gemObject *target, ProgressionManager *mgr, bool inverse)
    {
        // Remove this when adding support for the inverse operation
        if (inverse)
            return true;

        EntityManager * em = psserver->GetEntityManager();

        if (!actor)
        {
            Error2("Error: ProgressionEvent(%s) CreatePetOp needs an actor\n",eventName->GetData());
            return true;
        }

        if ( !actor->GetClientID() || !actor->GetClient() )
        {
            Error2("Error: ProgressionEvent(%s) CreatePetOp needs a valid client\n",eventName->GetData());
            return true;
        }

        int familiarid;
        value = (int)GetValue( actor, target, mgr);

        switch ( master_ids.Length() )
        {
        case 0:
            break;
        case 1:
            familiarid = master_ids[ 0 ];
            break;
        default:
            familiarid = (int) ( value / ( 100 / master_ids.Length() ) );
            if ( familiarid < 0 ) familiarid = 0;
            if ( familiarid > (int)master_ids.Length() ) familiarid =(int) master_ids.Length() - 1;
            familiarid = master_ids[ familiarid ];
            break;
        }
        
        gemNPC *Familiar = em->CreatePet( actor->GetClient(), familiarid );
        if ( Familiar == NULL ) 
        {
            Error2("Failed to create pet %d \n",familiarid);
            return false;
        }

        return true;
    }

    virtual bool Load(iDocumentNode *node, ProgressionManager *mgr,ProgressionEvent *script)
    {
        psString masterids( node->GetAttributeValue("master_ids") );
        psString token;
        int pos = 0;

        /* Establish string and get the first token: */
        masterids.GetWord( pos, token, psString::NO_PUNCT );
        while( token.Length() != 0 )
        {
            pos += (int)token.Length() + 1;
            master_ids.Push( atoi( token ) );
            masterids.GetWord( pos, token, psString::NO_PUNCT );
        }

        controlled = node->GetAttributeValueAsBool("controlled");
        
        return LoadValue(node,mgr,script);
    }

    virtual csString ToString()
    {
        return "<createpet />";
    }

protected:
    csArray<int> master_ids;
    bool controlled;
    int value;
    
};

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

/*-------------------------------------------------------------*/
/* Syntax:                                                     */
/*            <createfamiliar />                               */

class CreateFamiliarOp : public ProgressionOperation
{
public:

    virtual bool Run(gemActor * actor, gemObject *target, ProgressionManager *mgr, bool inverse)
    {
        if (!actor)
        {
            Error2( "Error: ProgressionEvent(%s) CreateFamiliar needs an actor\n",eventName->GetData());
            return true;
        }

        if ( !actor->GetClientID()) 
        {
            Error3( "Error: ProgressionEvent(%s) CreateFamiliar needs a valid client for actor '%s'.\n",eventName->GetData(),actor->GetName() );
            return true;
        }

        if ( actor->GetCharacterData()->GetFamiliarID() == 0 )
        {        
            gemNPC *Familiar = psserver->GetEntityManager()->CreateFamiliar( actor );
            if ( Familiar == NULL ) 
            {
                Error2( "Failed to create familiar for %s.\n", actor->GetName() );
                return false;
            }

            return true;
      
        }
        else
        {
            psserver->SendSystemInfo( actor->GetClientID(), "You already have a familiar, please take care of it.");
            return false;
        }
    }

    virtual bool Load(iDocumentNode *node, ProgressionManager *mgr,ProgressionEvent *script)
    {
        return true;
    }


    virtual csString ToString()
    {
        return "<createfamiliar />";
    }
};

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

/*-------------------------------------------------------------*/
/* Syntax:													   */
/*			<animalaffinity name=""
                            attribute="adjust|set"
                            value="##.##" />                   */

class AnimalAffinityOp : public ProgressionOperation
{
public:

    typedef enum { adjust_add, adjust_set } adjust_t;

    virtual bool Run( gemActor * actor, gemObject *target, ProgressionManager *mgr, bool inverse )
	{
		EntityManager * em = psserver->GetEntityManager();
        /// Pointer to the Crystal Space iDocumentSystem. 
        //csRef<iDocumentSystem>  xml = csPtr<iDocumentSystem>(new csTinyDocumentSystem);

        if ( !actor )
        {
            Error2( "Error: ProgressionEvent(%s) AnimalAffinityOp needs an actor\n", eventName->GetData() );
            return true;
        }

        Debug2(LOG_CHARACTER, actor->GetClientID(),"AnimalAffinityOp: %s \n", this->ToString().GetData() );

        //if ( !actor->GetClient() )
        //{
        //    CPrintf( CON_ERROR, "Error: ProgressionEvent(%s) AnimalAffinityOp needs a valid client\n", eventName->GetData() );
        //    return true;
        //}

		psCharacter *chardata = actor->GetCharacterData();
		csString animalAffinity = chardata->GetAnimalAffinity();

		if ( animalAffinity.Length() == 0 ) 
		{
			animalAffinity.Append("");
		}

		//     <category attribute="Type|Lifecycle|AttackTool|AttackType|..." name="" value="" />
		//     <category attribute="Type|Lifecycle|AttackTool|AttackType|..." name="" value="" />
        //     ...
		//     <category attribute="Type|Lifecycle|AttackTool|AttackType|..." name="" value="" />

		// Parse the string into an XML document.
        csRef<iDocumentSystem> xml = csPtr<iDocumentSystem>(new csTinyDocumentSystem);
        CS_ASSERT(xml != NULL);
        csRef<iDocument> xmlDoc  = xml->CreateDocument();
        const char* error = xmlDoc->Parse( animalAffinity );

		csRef<iDocumentNode> node;
        bool found = false;

        if ( !error )
        {
		    // Find existing node
		    csRef<iDocumentNodeIterator> iter = xmlDoc->GetRoot()->GetNodes();
		    while ( iter->HasNext() )
		    {
			    node = iter->Next();

			    csString operationStr = node->GetAttributeValue( "name" );
			    if ( operationStr.CompareNoCase( name ) )
			    {
				    found = true;
                    Debug3( LOG_PETS, actor->GetClientID(),"AnimalAffinityOp: %s : %s \n", name.GetData(), operationStr.GetData() );
				    break;
			    }
		    }
        }

		// Add new node if one doesn't exist
		if ( !found )
		{
            csString attrNode = mgr->GetAffinityCategories().Get( name.Downcase() , "" ).GetData();
            if ( attrNode.Length() != 0 )
            {
                node = xmlDoc->GetRoot()->CreateNodeBefore( CS_NODE_ELEMENT );
                node->SetValue( "category" );
                node->SetAttribute( "name" , name );
                node->SetAttribute( "attribute", attrNode );
                node->SetAttribute( "value" , "0" );
            }
            else
            {
                Error2( "Error: ProgressionEvent(%s) AnimalAffinityOp needs a valid category\n", eventName->GetData() );
                return false;
            }
		}

		// Modify Value
		if ( node )
		{
      float oldValue = node->GetAttributeValueAsFloat( "value" );
      float adjustValue = GetValue( actor, target, mgr);
      float newValue = CalcNewValue(oldValue, adjustValue, inverse, oldValue);

      Debug4( LOG_PETS, actor->GetClientID(),"AnimalAffinityOp: %f : %f : %f\n", oldValue, adjustValue, newValue );

			node->SetAttributeAsFloat( "value", newValue );
		}

		// Save changes back
		csRef<scfString> str;
		str.AttachNew( new scfString() );
		xmlDoc->Write( str );
		chardata->SetAnimialAffinity( str->GetData() );
    Debug2( LOG_PETS, actor->GetClientID(),"AnimalAffinityOp: %s \n", str->GetData() );

		return true;
    }


    float CalcNewValue(float oldValue, float adjustValue, bool inverse, float baseValue)
    {
        switch ( attribute )
        {
            case adjust_set: return adjustValue;
            case adjust_add: if ( inverse ) return oldValue - adjustValue; else return oldValue + adjustValue; 
        }
                        
        return 0.0;
    }

    virtual bool Load( iDocumentNode *node, ProgressionManager *mgr, ProgressionEvent *script )
    {		
        attribute = adjust_add;

        if (node->GetAttributeValue("attribute"))
        {
            csString adjustStr = node->GetAttributeValue("attribute");
            if (adjustStr == "set")
                attribute = adjust_set;
        }

        name = node->GetAttributeValue("name");

        return LoadValue( node, mgr, script );
        
        Debug2( LOG_PETS, 0,"AnimalAffinityOp: %s \n", this->ToString().GetData() );
    }


    virtual csString ToString()
    {
        psString attrStr, xml;
                
        switch (attribute)
        {
            case adjust_set: attrStr = "set"; break;
            case adjust_add: attrStr = "adjust"; break;
        }
		xml.Format( "<animalaffinity name='%s' attribute='%s' value='%.2f'/>", name.GetData(), attrStr.GetData(), script_text.GetData() );
		
		return xml;
    }

protected:
    adjust_t attribute; 
	csString name;
};

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

class ShowDetailsOp : public ProgressionOperation
{
public:
    csString * eventName;
    virtual bool Run(gemActor * actor, gemObject *target, ProgressionManager *mgr, bool inverse)
    {
        // Remove this when adding support for the inverse operation
        if (inverse)
            return true;

        gemActor * tgtAsActor;
        gemItem * tgtAsItem;
        Client * client;
        
        if (!actor)
        {
            Error2( "Error: ProgressionEvent(%s)  ShowDetailsOp needs an actor\n",
                    eventName->GetData());
            return true;
        }

        if (actor->GetCharacterData() == NULL)
            return true;
        
        client = psserver->GetConnections()->FindPlayer(actor->GetCharacterData()->GetCharacterID());
        if (client == NULL)
            return true;
        
        tgtAsActor = dynamic_cast<gemActor*>(target);
        if (tgtAsActor != NULL)
        {
            psserver->usermanager->SendCharacterDescription(client, tgtAsActor->GetCharacterData(), true, false, "ShowDetailsOp");
            return true;
        }
        
        tgtAsItem = dynamic_cast<gemItem*>(target);
        if (tgtAsItem != NULL)
        {
            //TOBEDONE
            return true;
        }
        return true;
    }
    virtual bool Load(iDocumentNode *node, ProgressionManager *mgr,ProgressionEvent *script)
    {
        return true;
    }
    virtual csString ToString()
    {
        return "<showdetails/>";
    }
};

/*-------------------------------------------------------------*/
/* Syntax:                                                     */
/*          <move type="reset|add|mod|const|push"              */
/*                duration="#" x="#" y="#" z="#" yrot="#" />   */

class MovementOp : public ProgressionOperation
{
public:
    MovementOp() : ProgressionOperation() { };
    virtual ~MovementOp() {};

    bool Load(iDocumentNode *node, ProgressionManager *mgr,ProgressionEvent *script)
    {
        type = node->GetAttributeValue("type");
        if (type == "reset")
        {
            typecode = psMoveModMsg::NONE;        // Remove all mods
            duration = 0;
            return true;  // All we need
        }
        else if (type == "add")
            typecode = psMoveModMsg::ADDITION;    // Add factor to moves
        else if (type == "mod")
            typecode = psMoveModMsg::MULTIPLIER;  // Multiply factor against moves
        else if (type == "const")
            typecode = psMoveModMsg::CONSTANT;    // Add factor until disabled
        else if (type == "push")
            typecode = psMoveModMsg::PUSH;        // Execute move once
        else
        {
            Error3( "Invalid type for ProgressionEvent(%s) MovementOp: %s\n", eventName->GetData(), type.GetData() );
            return true;
        }

        duration = node->GetAttributeValueAsInt("duration");
        
        if (typecode == psMoveModMsg::PUSH && duration != 0)
        {
            Error2("Duration cannot be used for type=\"push\" in ProgressionEvent(%s) MovementOp\n", eventName->GetData());
            return true;
        }

        moveMod.x = node->GetAttributeValueAsFloat("x");  // x-axis motion
        moveMod.y = node->GetAttributeValueAsFloat("y");  // y-axis motion
        moveMod.z = node->GetAttributeValueAsFloat("z");  // z-axis motion
        YrotMod = node->GetAttributeValueAsFloat("yrot"); // y-axis rotation

        // Make defaults for multiplication 1.0f instead of 0.0f
        if (typecode == psMoveModMsg::MULTIPLIER)
        {
            size_t i;
            for (i=0; i<3; i++)
                if (fabsf(moveMod[i]) < SMALL_EPSILON)
                    moveMod[i] = 1.0f;
            if (fabsf(YrotMod) < SMALL_EPSILON)
                YrotMod = 1.0f;
        }

        return true;
    }

    virtual csString ToString()
    {
        csString xml;
        xml.Format("<move type=\"%s\" ", type.GetData() );
        if (duration)
            xml.AppendFmt("duration=\"%d\" ", duration );
        if (typecode != psMoveModMsg::NONE)
            xml.AppendFmt("x=\"%.02f\" y=\"%.02f\" z=\"%.02f\" yrot=\"%.02f\" ", moveMod.x, moveMod.y, moveMod.z, YrotMod );
        xml.Append("/>");
        return xml;
    }

    bool Run(gemActor * actor, gemObject *target, ProgressionManager *mgr, bool inverse)
    {
        if (!actor)
        {
            Error2("Error: ProgressionEvent(%s) MovementOp needs an actor\n",eventName->GetData());
            return true;
        }

        Client* client = actor->GetClient();
        if (!client)
        {
            Error2( "Error: ProgressionEvent(%s) MovementOp needs a client\n",eventName->GetData());
            return true;
        }
        
        // Send modifier to client
        psMoveModMsg msg(client->GetClientNum(), typecode, moveMod, YrotMod);
        msg.SendMessage();

        if (duration != 0)
        {
            // Queue undo script
            mgr->QueueScript("<evt><move type=\"reset\" /></evt>",duration*1000,actor,actor);
        }

        return true;
    }

protected:
    csString type;                   /// Type of modifier
    psMoveModMsg::ModType typecode;  /// Type code from msg enum
    int duration;                    /// Duration of effect in seconds
    csVector3 moveMod;               /// Movement modifier
    float YrotMod;                   /// Rotation modifier
};

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

psScriptGameEvent::psScriptGameEvent(csTicks offsetticks,
                                     ProgressionManager *mgr,
                                     ProgressionEvent * script,
                                     gemActor *actor,
                                     gemObject *target,
                                     bool persistent)
    : psGEMEvent(0,offsetticks,target,"psScriptGameEvent"),persistent(persistent)
{
    this->mgr      = mgr;
    this->script   = script;
    this->actor    = (actor) ? actor->GetSafeReference() : NULL ;
    this->target   = (target) ? target->GetSafeReference() : NULL ;
}

/** 
 * Called when the target object disconnect. It will than store
 * the progression script in the DB to be executed when character
 * reconnect. There are no way to remove events from the event queue
 * so by setting script to NULL, the event is than marked for 
 * no execution when it is triggered.
 */
void psScriptGameEvent::DeleteObjectCallback(iDeleteNotificationObject * object)
{
    psGEMEvent::DeleteObjectCallback(object);  // This removes from callback list.

    csString status;
    status.Format("Debug: Deleting script %pp for object %pp", this, object);
    psserver->GetLogCSV()->Write(CSV_STATUS, status);

    csString xml;

    // Store script to character if persistent
    if (persistent)
    {
        csTicks remindingTime = triggerticks - csGetTicks();
    
    csString script_str = script->ToString(false);
        xml.Format("<script delay=\"%d\">%s</script>",
                   remindingTime,script_str.GetData());
        
        // Store the progression script
        psCharacter * character = (target->gemobj)? target->gemobj->GetCharacterData() : NULL;
        if (character)
        {
            character->AppendProgressionScript(xml);
        }
        else
        {
            Error2("Can't store progression script, because target %s has no character.",
                (target->gemobj)? target->gemobj->GetName():"Actor already removed)");
        }
    }

    // There are now way to remove a queued game event so we mark it so 
    // it dosn't execute the script later.
    script = NULL;
}

void psScriptGameEvent::Trigger()
{
    // This event have been terminated by a disconnecting target.
    if (script == NULL)
        return;
    
    if (target && target->gemobj)
    {
        gemActor* act = (actor && actor->gemobj) ? dynamic_cast<gemActor*>(actor->gemobj) : NULL ;
        script->Run( act, target->gemobj, mgr );
    }
}



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

ProgressionManager::ProgressionManager(ClientConnectionSet *ccs)
{
    clients      = ccs;

    psserver->GetEventManager()->Subscribe(this,MSGTYPE_GUISKILL,REQUIRE_READY_CLIENT);
    
    psserver->GetEventManager()->Subscribe(this,MSGTYPE_DEATH_EVENT,NO_VALIDATION);
    psserver->GetEventManager()->Subscribe(this,MSGTYPE_ZPOINT_EVENT,NO_VALIDATION);

    Initialize();  // Load from db.
}


ProgressionManager::~ProgressionManager()
{
    psserver->GetEventManager()->Unsubscribe(this,MSGTYPE_GUISKILL);
    psserver->GetEventManager()->Unsubscribe(this,MSGTYPE_DEATH_EVENT);
    psserver->GetEventManager()->Unsubscribe(this,MSGTYPE_ZPOINT_EVENT);    
}

void ProgressionManager::QueueScript(const char *scriptText, int delay, gemActor * actor, gemObject *target)
{
    csRef<iDocument> doc = ParseString(scriptText);
    if (doc == NULL)
    {
        Error2("Failed to parse script %s", scriptText);
        return;
    }
    ProgressionEvent * script =  new ProgressionEvent();
    if (!script->LoadScript(doc,this))
    {
        Error2("Failed to load script %s", scriptText);
        return;
    }
    psScriptGameEvent * event =
            new psScriptGameEvent(delay,this,script,actor,target,true);
    psserver->GetEventManager()->Push(event);
}

void ProgressionManager::Initialize()
{
    Result result_factions(db->Select("SELECT * from factions"));

    unsigned int x = 0;

    if ( result_factions.IsValid() )
    {
        for ( x = 0; x < result_factions.Count(); x++ )
        {   
            Faction *f = new Faction;
            f->id = atoi( result_factions[x]["id"] );
            f->name = result_factions[x]["faction_name"];
            f->weight = atof( result_factions[x]["faction_weight"] );
            // Stored two different ways
            factions.Insert(f,TREE_OWNS_DATA);
            factions_by_id.Put(f->id,f);
        }    
    }


    Result result_events(db->Select("SELECT * from progression_events"));

    if ( result_events.IsValid() )
    {
        csRef<iDocumentSystem> xml = csPtr<iDocumentSystem>(new csTinyDocumentSystem);

        for ( x = 0; x < result_events.Count(); x++ )
        {
            ProgressionEvent *ev = new ProgressionEvent;
            ev->name = result_events[x]["name"];

            csRef<iDocument> doc = xml->CreateDocument();
            const char* error = doc->Parse( result_events[x]["event_script"] );
            if ( error )
            {
                Error2("Could not parse the event named %s. Reason: ", ev->name.GetData() );               
                Error3("%s\n%s",error,result_events[x]["event_script"]);
                delete ev;
                continue;
            }
            if (ev->LoadScript(doc,this))
            {
                events.Insert(ev,TREE_OWNS_DATA);
            } else {
                Error2("Coudn't load script %s\n",ev->name.GetData());
                delete ev;
            }
        }    
    }

    Result result_affinitycategories(db->Select("SELECT * from char_create_affinity"));

    if ( result_affinitycategories.IsValid() )
    {
        for ( x = 0; x < result_affinitycategories.Count(); x++ )
        {   
            affinitycategories.Put( csString( result_affinitycategories[x]["category"]).Downcase() , csString( result_affinitycategories[x]["attribute"]).Downcase() );            
        }    
    }
}

void ProgressionManager::HandleMessage(MsgEntry *me,Client *client)
{
    switch( me->GetType())
    {
        case MSGTYPE_GUISKILL:
        {
            psGUISkillMessage msg(me);
            if (msg.valid)
                HandleSkill(client, msg);
            else
            {
                Debug2(LOG_NET,me->clientnum,"Received unparsable psGUISkillMessage from client %u.\n",me->clientnum);
            }
            break;
        }
        case MSGTYPE_DEATH_EVENT:
        {
            HandleDeathEvent(me);
            break;
        }
        
        case MSGTYPE_ZPOINT_EVENT:
        {
            psZPointsGainedEvent evt(me);
            
            Client* client = clients->Find( evt.actor->GetClientID() );

            if (!client)
            {
                Error1("Got unknown client!");
                return;
            }

            csString string;
            string.Format("You've gained some practice points in %s. ", evt.skillName.GetData() );
            if ( evt.rankUp )
            {
                string.Append("You've also Ranked up!");                    
            }
            psserver->SendSystemInfo(evt.actor->GetClientID(), string);            

            SendSkillList( client, false );
            
            break;
        }
        
    }
}

void ProgressionManager::HandleDeathEvent(MsgEntry *me)
{
    Debug1(LOG_COMBAT, me->clientnum,"Progression Manager handling Death Event\n");
    psDeathEvent evt(me);

    // Only do progression if dead guy is an NPC and not a pet
    if (evt.deadActor->GetClientID()==0 && !evt.deadActor->GetCharacterData()->IsPet())
    {
        csString progEvent = FindEvent( "kill" )->ToString(true);
        
        // Convert the exp to a char
        csString buffer;
        buffer.Format("%lu",(unsigned long) evt.deadActor->GetCharacterData()->GetKillExperience());

        ChangeScript( progEvent, 0, (const char*)buffer );
        ProcessScript(progEvent,evt.killer,evt.deadActor);
    }
}


void ProgressionManager::HandleSkill(Client * client, psGUISkillMessage& msg)
{
    //    CPrintf(CON_DEBUG, "ProgressionManager::HandleSkill(%d,%s)\n",msg.command, (const char*)msg.commandData);
    switch ( msg.command )
    {
        case psGUISkillMessage::REQUEST:
        {
            // Clear the current skill cache
            psCharacter * character = client->GetCharacterData();
            if (character)
                character->GetSkillCache()->clear();

            SendSkillList(client,false);
            break;
        }
        case psGUISkillMessage::SKILL_SELECTED:
        {
            csRef<iDocumentSystem> xml = csPtr<iDocumentSystem>(new csTinyDocumentSystem);

            CS_ASSERT( xml );
            
            csRef<iDocument> invList  = xml->CreateDocument();

            const char* error = invList->Parse( msg.commandData);
            if ( error )
            {
                Error2("Error in XML: %s", error );
                return;
            }

            csRef<iDocumentNode> root = invList->GetRoot();
            if(!root)
            {
                Error1("No XML root");
                return;
            }
            csRef<iDocumentNode> topNode = root->GetNode("S");
            if(!topNode)
            {
                Error1("No <S> tag");
                return;
            }
            
            csString skillName = topNode->GetAttributeValue("NAME");

            psSkillInfo * info = CacheManager::GetSingleton().GetSkillByName(skillName);

            csString buff;
            if (info)
            {
                buff.Format("<DESCRIPTION NAME=\"%s\" DESC=\"%s\" CAT=\"%d\"/>",
                            EscpXML(skillName).GetData(), EscpXML(info->description).GetData(),
                            info->category);
            }
            else
            {
                buff.Format("<DESCRIPTION NAME=\"%s\" DESC=\"\" CAT=\"%d\"/>",
                            EscpXML(skillName).GetData(), info->category);
            }

            psCharacter* chr = client->GetCharacterData();
            psGUISkillMessage newmsg(client->GetClientNum(),
                            psGUISkillMessage::DESCRIPTION,
                            buff,
                            NULL,
                            int(chr->GetAttributes()->GetStat(PSITEMSTATS_STAT_STRENGTH)),
                            int(chr->GetAttributes()->GetStat(PSITEMSTATS_STAT_ENDURANCE)),
                            int(chr->GetAttributes()->GetStat(PSITEMSTATS_STAT_AGILITY)),
                            int(chr->GetAttributes()->GetStat(PSITEMSTATS_STAT_INTELLIGENCE)),
                            int(chr->GetAttributes()->GetStat(PSITEMSTATS_STAT_WILL)),
                            int(chr->GetAttributes()->GetStat(PSITEMSTATS_STAT_CHARISMA)),
                            int(chr->GetHP()),
                            int(chr->GetMana()),
                            int(chr->GetStamina(true)),
                            int(chr->GetStamina(false)),
                            int(chr->GetHitPointsMax()),
                            int(chr->GetManaMax()),
                            int(chr->GetStaminaMax(true)),
                            int(chr->GetStaminaMax(false)),
                            float(chr->duel_points),
                            true,
                            PSSKILL_NONE,
                            -1,
                            false);
            
            if (newmsg.valid)
                SendMessage(newmsg.msg);
            else
            {
                Bug2("Could not create valid psGUISkillMessage for client %u.\n",client->GetClientNum());
            }
            break;
        }
        case psGUISkillMessage::BUY_SKILL:
        {
            Debug1(LOG_SKILLXP, client->GetClientNum(),"---------------Buying Skill-------------\n");
            csRef<iDocumentSystem> xml = csPtr<iDocumentSystem>(new csTinyDocumentSystem);

            CS_ASSERT( xml );
            
            csRef<iDocument> invList  = xml->CreateDocument();

            const char* error = invList->Parse( msg.commandData);
            if ( error )
            {
                Error2("Error in XML: %s", error );
                return;
            }

            csRef<iDocumentNode> root = invList->GetRoot();
            if(!root)
            {
                Error1("No XML root");
                return;
            }
            csRef<iDocumentNode> topNode = root->GetNode("B");
            if(!topNode)
            {
                Error1("No <B> tag");
                return;
            }
            
            csString skillName = topNode->GetAttributeValue("NAME");
            
            psSkillInfo * info = CacheManager::GetSingleton().GetSkillByName(skillName);
            Debug2(LOG_SKILLXP, client->GetClientNum(),"    Looking for: %s\n", (const char*)skillName);
            
            if (!info)
            {
                Error2("No skill with name %s found!",skillName.GetData());
                Error2("Full Data Sent from Client was: %s\n", msg.commandData.GetData() );
                return;
            }

            psCharacter * character = client->GetCharacterData();

            if (character->GetTrainer() == NULL)
            {
                psserver->SendSystemInfo(client->GetClientNum(),
                                         "Can't buy skills when not training!");
                return;
            }
            
            gemActor* actorTrainer = character->GetTrainer()->GetActor();
            if ( actorTrainer )
            {
                if ( character->GetActor()->RangeTo(actorTrainer, false) > RANGE_TO_SELECT )
                {
                    psserver->SendSystemInfo(client->GetClientNum(),
                                             "Need to get a bit closer to understand the training.");
                    return;                    
                }
            }

            
            
            Debug2(LOG_SKILLXP, client->GetClientNum(),"    PP available: %d\n", character->GetProgressionPoints() );
            
            // Test for progression points
            if (character->GetProgressionPoints() <= 0)
            {
                psserver->SendSystemInfo(client->GetClientNum(),
                                         "You don't have any progression points!");
                return;
            }

            // Test for money
            
            if (info->price > character->Money())
            {
                psserver->SendSystemInfo(client->GetClientNum(),
                                         "You don't have the money to buy this skill!");
                return;
            }
            if ( !character->CanTrain( info->id ) )
            {
                psserver->SendSystemInfo(client->GetClientNum(),
                                         "You cannot train this skill any higher yet!");
                return;            
            }
            
            int current = character->GetSkills()->GetSkillRank((PSSKILL)info->id);
            float faction = actorTrainer->GetRelativeFaction(character->GetActor());
            if ( !character->GetTrainer()->GetTrainerInfo()->TrainingInSkill((PSSKILL)info->id, current, faction))
            {
                psserver->SendSystemInfo(client->GetClientNum(),
                                         "You cannot train this skill currently.");
                return;            
            }

            character->UseProgressionPoints(1);
            character->SetMoney(character->Money()-info->price);            
            character->Train(info->id,1);
            SendSkillList(client,true,info->id);
            
            psserver->SendSystemInfo(client->GetClientNum(), "You've received some %s training", skillName.GetData());
            
            break;
        }
        case psGUISkillMessage::QUIT:
        {
            client->GetCharacterData()->SetTrainer(NULL);
            client->GetCharacterData()->GetSkillCache()->clear();
            break;
        }
    }
}

void ProgressionManager::SendSkillList(Client * client, bool forceOpen, PSSKILL focus, bool isTraining )
{
    psCharacter * character = client->GetCharacterData();
    psCharacter * trainer = character->GetTrainer();
    psTrainerInfo * trainerInfo = NULL;
    float faction = 0.0;
    int selectedSkillCat = -1; //This is used for storing the category of the selected skill
    int selectedSkillNameId = -1; // Name ID value of the selected skill

    // Get the current skill cache
    psSkillCache *skills = character->GetSkillCache();

    skills->setProgressionPoints(character->GetProgressionPoints());

    if (trainer)
    {
        trainerInfo = trainer->GetTrainerInfo();
        faction = trainer->GetActor()->GetRelativeFaction(character->GetActor());
    }


    for (int skillID = 0; skillID < (int)PSSKILL_COUNT; skillID++)
    {
        psSkillInfo * info = CacheManager::GetSingleton().GetSkillByID(skillID);
        if (!info)
        {
            Error2("Can't find skill %d",skillID);
            return;
        }

        Skill * charSkill = character->GetSkills()->GetSkill( (PSSKILL)skillID );
        if (charSkill == NULL)
        {
            Error3("Can't find skill %d in character %i",skillID, character->characterid);
            return;
        }

        // If we are training, send skills that the trainer is providing education in only
        if  (
                !trainerInfo
                    ||
                trainerInfo->TrainingInSkill((PSSKILL)skillID, character->GetSkills()->GetSkillRank((PSSKILL)skillID), faction)
             )
        {
            bool stat = info->id == PSSKILL_AGI ||
                        info->id == PSSKILL_CHA ||
                        info->id == PSSKILL_END ||
                        info->id == PSSKILL_INT ||
                        info->id == PSSKILL_WILL ||
                        info->id == PSSKILL_STR;

            /* Get the ID value for the skill name string and find the skill
               in the cache. If it can't be found, skip this skill.
            */
            unsigned int skillNameId =
                    CacheManager::GetSingleton().FindCommonStringID(info->name);

            if (skillNameId == 0)
            {
                Error2("Can't find skill name \"%s\" in common strings", info->name.GetData());
                continue;
            }

            psSkillCacheItem *item = skills->getItemBySkillId(skillID);

            if (info->id == focus)
            {
                selectedSkillNameId = (int)skillNameId;
                selectedSkillCat=info->category;
            }

            int actualStat = 0;

            if (info->category == 0)
            { 
                if(info->name=="Strength")
                {
                    actualStat=character->GetAttributes()->GetStat(PSITEMSTATS_STAT_STRENGTH);
                }
                else if(info->name== "Endurance")
                {
                    actualStat=character->GetAttributes()->GetStat(PSITEMSTATS_STAT_ENDURANCE);
                } 
                else if(info->name== "Agility")
                {
                    actualStat=character->GetAttributes()->GetStat(PSITEMSTATS_STAT_AGILITY);
                }
                else if(info->name== "Intelligence")
                {
                    actualStat=character->GetAttributes()->GetStat(PSITEMSTATS_STAT_INTELLIGENCE);
                }
                else if(info->name== "Will")
                {
                    actualStat=character->GetAttributes()->GetStat(PSITEMSTATS_STAT_WILL);
                }
                else
                {   
                    actualStat=character->GetAttributes()->GetStat(PSITEMSTATS_STAT_CHARISMA);
                }

            }

            if (item)
            {
                item->update(charSkill->rank, actualStat,
                             charSkill->y, charSkill->yCost,
                             charSkill->z, charSkill->zCost);
            } else
            {
                item = new psSkillCacheItem(skillID, skillNameId,
                                            charSkill->rank, actualStat,
                                            charSkill->y, charSkill->yCost,
                                            charSkill->z, charSkill->zCost,
                                            info->category, stat);
                skills->addItem(skillID, item);
            }
        } else if (trainerInfo)
        {
            // We are training, but this skill is not available for training
            psSkillCacheItem *item = skills->getItemBySkillId(skillID);
            if (item)
                item->setRemoved(true);
        }
    }

    bool training= false;
    if (isTraining)
        training= true;

    psGUISkillMessage newmsg(client->GetClientNum(),
                            psGUISkillMessage::SKILL_LIST,
                            "",
                            skills,
                            (unsigned int)character->GetAttributes()->GetStat(PSITEMSTATS_STAT_STRENGTH),
                            (unsigned int)character->GetAttributes()->GetStat(PSITEMSTATS_STAT_ENDURANCE),
                            (unsigned int)character->GetAttributes()->GetStat(PSITEMSTATS_STAT_AGILITY),
                            (unsigned int)character->GetAttributes()->GetStat(PSITEMSTATS_STAT_INTELLIGENCE),
                            (unsigned int)character->GetAttributes()->GetStat(PSITEMSTATS_STAT_WILL),
                            (unsigned int)character->GetAttributes()->GetStat(PSITEMSTATS_STAT_CHARISMA),
                            (unsigned int)character->GetHP(),
                            (unsigned int)character->GetMana(),
                            (unsigned int)character->GetStamina(true),
                            (unsigned int)character->GetStamina(false),
                            (unsigned int)character->GetHitPointsMax(),
                            (unsigned int)character->GetManaMax(),
                            (unsigned int)character->GetStaminaMax(true),
                            (unsigned int)character->GetStaminaMax(false),
                            character->duel_points,
                            forceOpen,
                            selectedSkillNameId,
                            selectedSkillCat,
                            training //If we are training the client must know it
                            );

    Debug2(LOG_SKILLXP, client->GetClientNum(),"Sending psGUISkillMessage w/ stats to %d, Valid: ",int(client->GetClientNum()));
    if (newmsg.valid)
    {
        Debug1(LOG_SKILLXP, client->GetClientNum(),"Yes\n");
        SendMessage(newmsg.msg);

    }
    else
    {
        Debug1(LOG_SKILLXP, client->GetClientNum(),"No\n");
        Bug2("Could not create valid psGUISkillMessage for client %u.\n",client->GetClientNum());
    }
}

void ProgressionManager::StartTraining(Client * client, psCharacter * trainer)
{
    client->GetCharacterData()->SetTrainer(trainer);
    SendSkillList(client, true, PSSKILL_NONE, true);
}


float ProgressionManager::ProcessEvent(const char *event, gemActor * actor, gemObject *target)
{
    ProgressionEvent * ev = FindEvent(event);
    if (ev)
    {
        return ProcessEvent(ev,actor,target);
    }

    csString actorName = "N/A";
    csString targetName = "N/A";
    
    if ( actor )
        actorName = actor->GetName();
    if ( target )
        targetName = target->GetName();
    
    Error4( "Error: Can't find progression event: %s from actor [%s] on target [%s]\n", event, actorName.GetData(), targetName.GetData());

    return 0.0;
}

float ProgressionManager::ProcessEvent(ProgressionEvent * ev, gemActor * actor, gemObject *target, bool inverse)
{
    Debug5(LOG_SPELLS, actor?actor->GetClientID():0,"Process %s event %s on %s with target %s.\n",
      inverse ? "inverse" : "", ev->name.GetData(),(actor?actor->GetName():"(null)"),
            (target?target->GetName():"(null)"));
            
    return ev->Run(actor,target,this,inverse);
}

ProgressionEvent * ProgressionManager::CreateEvent(const char *name, const char *script)
{
    ProgressionEvent *ev = new ProgressionEvent;
    // Create a Uniq event name!!
    int count = 0;
    do {
        count ++;
        int uniq_number = (int)(9999*psserver->GetRandom());
        ev->name.Format("%s%04d",name,uniq_number++);
    } while (FindEvent(ev->name) != NULL && count < 100);
    if (count >= 100)
    {
        Error2("Uniq number not found. Failed to execute script: %s",script);
        return NULL;
    }

    csRef<iDocumentSystem> xml = csPtr<iDocumentSystem>(new csTinyDocumentSystem);
    csRef<iDocument> doc = xml->CreateDocument();
    const char* error = doc->Parse( script );
    if ( error )
    {
        Error3("%s\n%s",error, script );
        delete ev;
        return NULL;
    }
    if (ev->LoadScript(doc,this))
    {
        events.Insert(ev,TREE_OWNS_DATA);
    }
    else
    {
        Error2("Faild to load %s", script );
        delete ev;
        return NULL;
    }

    return ev;
}


float ProgressionManager::ProcessScript(const char *script, gemActor * actor, gemObject *target)
{
    ProgressionEvent * ev = CreateEvent((actor?actor->GetName():"Unknown"),script);
    
    if (ev)
        return ProcessEvent(ev->name,actor,target);

    return 0.0;
}

bool ProgressionManager::AddScript(const char *name, const char *script)
{
    ProgressionEvent *ev = new ProgressionEvent;

    csRef<iDocumentSystem> xml = csPtr<iDocumentSystem>(new csTinyDocumentSystem);
    csRef<iDocument> doc = xml->CreateDocument();
    const char* error = doc->Parse( script );
    if ( error )
    {
        Error3("Adding script error: %s\n%s",error, script );
        delete ev;
        return false;
    }
    ev->name = name;

    if (ev->LoadScript(doc,this))
        events.Insert(ev,TREE_OWNS_DATA);
    csString escText;
    db->Escape( escText, script );
    unsigned long res = db->Command("INSERT INTO progression_events "
                             "(name,event_script) "
                             "VALUES (\"%s\",\"%s\")",name, escText.GetData());
    if(res!=1)
        return false;
    else
        return true;
}


ProgressionEvent *ProgressionManager::FindEvent(char const *name)
{
    if ( name == NULL ) return NULL;
    ProgressionEvent ev;
    ev.name = name;
    return events.Find(&ev);
}

Faction *ProgressionManager::FindFaction(const char *name)
{
    Faction fac;
    fac.name = name;
    return factions.Find(&fac);
}

Faction *ProgressionManager::FindFaction(int id)
{
    return factions_by_id.Get(id,0);
}

void ProgressionManager::QueueEvent(psGameEvent *event)
{
    psserver->GetEventManager()->Push(event);
}

void ProgressionManager::SendMessage(MsgEntry *me)
{
    psserver->GetEventManager()->SendMessage(me);
}

void ProgressionManager::Broadcast(MsgEntry *me)
{
    psserver->GetEventManager()->Broadcast(me, NetBase::BC_EVERYONE);
}

void ProgressionManager::ChangeScript( csString& script, int param, const char* text )
{
    psString scriptString( script );
    csString buff;
    buff.Format("$%d", param );
     
    scriptString.ReplaceAllSubString( buff, text );
    script.Replace( scriptString.GetData() );
}

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

ProgressionEvent::~ProgressionEvent()
{
    while (sequence.Length())
    {
        delete sequence.Pop();
    }
    while (variables.Length())
    {
        delete variables.Pop();
    }
}


bool ProgressionEvent::LoadScript(iDocument *doc,ProgressionManager *mgr)
{
    csRef<iDocumentNode> root    = doc->GetRoot();
    if(!root)
    {
        Error1("No XML root in progression script");
        return false;
    }
    csRef<iDocumentNode> topNode = root->GetNode("evt");
    if (topNode)
        return LoadScript(topNode,mgr);
    else
    {
        Error1("Could not find <evt> tag in progression script!");
        return false;
    }
}

bool ProgressionEvent::LoadScript(iDocumentNode * topNode, ProgressionManager *mgr)
{
    csRef<iDocumentNodeIterator> iter = topNode->GetNodes();

    while ( iter->HasNext() )
    {
        csRef<iDocumentNode> node = iter->Next();

        if ( node->GetType() != CS_NODE_ELEMENT )
            continue;

        ProgressionOperation * op = NULL;

        // This is a widget so read it's factory to create it.
        if ( strcmp( node->GetValue(), "agi" ) == 0 )
        {
            op = new StatsOp(StatsOp::AGI);
        } 
        else if ( strcmp( node->GetValue(), "cha" ) == 0 )
        {
            op = new StatsOp(StatsOp::CHA);
        } 
        else if ( strcmp( node->GetValue(), "end" ) == 0 )
        {
            op = new StatsOp(StatsOp::END);
        } 
        else if ( strcmp( node->GetValue(), "exp" ) == 0 )
        {
            op = new ExperienceOp;
        } 
        else if ( strcmp( node->GetValue(), "faction" ) == 0 )
        {
            op = new FactionOp;
        } 
        else if ( strcmp( node->GetValue(), "pstamina" ) == 0 )
        {
            op = new StatsOp(StatsOp::PSTAMINA);
        } 
        else if ( strcmp( node->GetValue(), "mstamina" ) == 0 )
        {
            op = new StatsOp(StatsOp::MSTAMINA);
        } 
        else if ( strcmp( node->GetValue(), "hp" ) == 0 )
        {
            op = new StatsOp(StatsOp::HP);
        } 
        else if ( strcmp( node->GetValue(), "int" ) == 0 )
        {
            op = new StatsOp(StatsOp::INT);
        } 
        else if ( strcmp( node->GetValue(), "item" ) == 0 )
        {
            op = new ItemOp();
        } 
        else if ( strcmp( node->GetValue(), "mana" ) == 0 )
        {
            op = new StatsOp(StatsOp::MANA);
        } 
        else if ( strcmp( node->GetValue(), "msg" ) == 0 )
        {
             op = new MsgOp();
        } 
        else if ( strcmp( node->GetValue(), "block" ) == 0 )
        {
             op = new BlockOp();
        } 
        else if ( strcmp( node->GetValue(), "purify" ) == 0 )
        {
            op = new PurifyOp();
        } 
        else if ( strcmp( node->GetValue(), "script" ) == 0 )
        {
            op = new ScriptOp();
        } 
        else if ( strcmp( node->GetValue(), "area" ) == 0 )
        {
            op = new AreaOp();
        }
        else if ( strcmp( node->GetValue(), "str" ) == 0 )
        {
            op = new StatsOp(StatsOp::STR);
        } 
        else if ( strcmp( node->GetValue(), "wil" ) == 0 )
        {
            op = new StatsOp(StatsOp::WIL);
        } 
        else if ( strcmp( node->GetValue(), "con" ) == 0 )
        {
            op = new StatsOp(StatsOp::CON);
        } 
        else if ( strcmp( node->GetValue(), "sta" ) == 0 )
        {
            op = new StatsOp(StatsOp::STA);
        } 
        else if ( strcmp( node->GetValue(), "attack" ) == 0 )
        {
            op = new StatsOp(StatsOp::ATTACK);
        }
        else if ( strcmp( node->GetValue(), "defense" ) == 0 )
        {
            op = new StatsOp(StatsOp::DEFENSE);
        }
        else if ( strcmp( node->GetValue(), "hpRate" ) == 0 )
        {
            op = new StatsOp(StatsOp::HPRATE);
        }
        else if ( strcmp( node->GetValue(), "skill" ) == 0 )
        {
            op = new SkillOp();
        }
        else if ( strcmp( node->GetValue(), "showdetails" ) == 0 )
        {
            op = new ShowDetailsOp();
        }
        else if ( strcmp( node->GetValue(), "attachscript" ) == 0 )
        {
            op = new AttachScriptOp();
        } 
        else if ( strcmp( node->GetValue(), "detachscript" ) == 0 )
        {
            op = new DetachScriptOp();
        } 
        else if ( strcmp( node->GetValue(), "identifymagic" ) == 0 )
        {
            op = new IdentifyMagicOp();
        } 
        else if ( strcmp( node->GetValue(), "createnpc" ) == 0 )
        {
            op = new CreatePetOp();
        }
        else if ( strcmp( node->GetValue(), "createpet" ) == 0 )
        {
            op = new CreatePetOp();
        }
        else if ( strcmp( node->GetValue(), "createfamiliar" ) == 0 )
        {
            op = new CreateFamiliarOp();
        }
        else if ( strcmp( node->GetValue(), "move" ) == 0 )
        {
            op = new MovementOp();
        }
        else if ( strcmp( node->GetValue(), "trait" ) == 0 )
        {
            op = new TraitChangeOp();
        }         
        else if ( strcmp( node->GetValue(), "animalaffinity" ) == 0 )
        {
            op = new AnimalAffinityOp();
        }
        else if ( strcmp( node->GetValue(), "morph" ) == 0 )
        {
            op = new MorphOp();
        }
        else if ( strcmp( node->GetValue(), "set" ) == 0 )
        {
            op = new AttributeOp();
        }
        else if ( strcmp( node->GetValue(), "weather" ) == 0 )
        {
            op = new WeatherOp();
        }
        else
        {
//            op = ExpressionOp::Factory(node,mgr);
            Error3("Unknown script operation %s in script %s.",node->GetValue(), name.GetData() );
            return false;
        }

        op->eventName = &name;
        
        if (op->Load(node,mgr,this))
            sequence.Push(op);
        else
        {
            delete op;
            return false;
        }
    }
    return true;
}

csString ProgressionEvent::ToString(bool topLevel) const
{
    csString xml;
    if (topLevel) xml.Append("<evt>");
    csArray<ProgressionOperation*>::ConstIterator seq = sequence.GetIterator();
    while (seq.HasNext())
    {
        ProgressionOperation * po = seq.Next();
        xml.Append(po->ToString());
    }
    if (topLevel) xml.Append("</evt>");
    return xml;
}

void ProgressionEvent::CopyVariables(MathScript *from)
{
    csHash<MathScriptVar*>::GlobalIterator iter = from->variables.GetIterator();
    while (iter.HasNext())
    {
        MathScriptVar *ptr = iter.Next();
        MathScriptVar *mine = FindVariable(ptr->name);
        if (!mine)
        {
            mine = new MathScriptVar;
            variables.Push(mine);
        }
        mine->Copy(ptr);
    }
}

void ProgressionEvent::LoadVariables(MathScript *script)
{
    if (!script)
        return; 

    size_t i;
    for (i=0; i<variables.Length(); i++)
    {
        MathScriptVar *var = script->GetOrCreateVar(variables[i]->name);
        var->SetValue(variables[i]->GetValue() );
    }
}

MathScriptVar *ProgressionEvent::FindOrCreateVariable(const char *name)
{
    MathScriptVar *found = FindVariable(name);
    if (found)
        return found;

    MathScriptVar *pv = new MathScriptVar;
    pv->name = name;
    pv->SetValue(0);
    AddVariable(pv);

    return pv;
}

void ProgressionEvent::AddVariable(MathScriptVar *pv)
{
    MathScriptVar *found = FindVariable(pv->name);
    if (!found)
        variables.Push(pv);
    else
        found->Copy(pv);
}


void ProgressionEvent::SetValue( const char* name, double val )
{
    for( size_t z = 0; z < sequence.Length(); z++ )
    {
        MathScript* script = sequence[z]->GetMathScript();
        if ( script )
        {
            MathScriptVar* var = script->GetVar( name );
            if ( var )
            {
                var->SetValue( val );
            }                
        }
    }    
}

MathScriptVar *ProgressionEvent::FindVariable(const char *name)
{
    size_t i;
    
    for (i=0; i<variables.Length(); i++)
    {
        if (variables[i]->name == name)
            return variables[i];
    }
    return NULL;
}


float ProgressionEvent::Run(gemActor * actor, gemObject *target, ProgressionManager *mgr, bool inverse)
{
    float result = 0.0f;

    csString dump_str = Dump();
    Notify5(LOG_SCRIPT,"%s run this script %son %s:\n%s",
            (actor?actor->GetName():""),
            (inverse?"inverse ":""),(target?target->GetName():""),
            dump_str.GetData());

    csArray<ProgressionOperation*>::Iterator seq = sequence.GetIterator();
    while (seq.HasNext())
    {
        ProgressionOperation * po = seq.Next();
        po->LoadVariables(variables);
        if ( po->Run(actor,target,mgr,inverse) == false ) break;
        result += po->GetResult();
    }
    return result;
}

csString ProgressionEvent::Dump() const
{
    csString str;
    str.Append("Name  : ");
    str.Append(name.GetData());
    str.Append("\nScript:\n");
    str.Append(ToString(true));
    str.Append("\nVariables:\n");
    for (size_t i=0; i < variables.Length(); i++)
    {
        str.Append(variables[i]->Dump());
        str.Append("\n");
    }
    return str;
}
