/*
 * psspell.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 <physicallayer/entity.h>
#include <propclass/mesh.h>

#include "util/log.h"
#include "util/strutil.h"
#include "util/serverconsole.h"

#include "../globals.h"
#include "../psserver.h"
#include "../gem.h"
#include "../client.h"
#include "../cachemanager.h"
#include "../progressionmanager.h"
#include "../npcmanager.h"
#include "util/psdatabase.h"
#include "../playergroup.h"

#include "psspell.h"
#include "psglyph.h"
#include "../spellmanager.h"
#include "util/psxmlparser.h"
#include "util/mathscript.h"
#include "psguildinfo.h"

#define SPELL_TOUCH_RANGE   3.0

psSpell::psSpell()
{
    saveThrow = 0;
}

psSpell::~psSpell()
{
    delete saveThrow;
}

bool psSpell::Load(iResultRow& row)
{
    id = row.GetInt("id");

    name =row["name"];

    way = CacheManager::GetSingleton().GetWayByID(row.GetInt("way_id"));
    caster_effect     = row["caster_effect"];
    target_effect     = row["target_effect"];
    image               = row["image_name"];
    description         = row["spell_description"];
    progression_event   = row["progression_event"];
    saved_progression_event = row["saved_progression_event"];


    casting_duration    = row.GetInt("casting_duration");
    realm               = row.GetInt("realm");
    interval_time       = row.GetInt("interval_time");
    max_power           = row.GetInt("max_power");
    offensive           = row.GetInt("offensive") ? true : false;
    spell_target        = row.GetInt("target_type");

    npcSpellCategoryID    = row.GetInt("cstr_npc_spell_category");
    npcSpellRelativePower = row.GetFloat("npc_spell_power");
    npcSpellCategory      = CacheManager::GetSingleton().FindCommonString(npcSpellCategoryID);

    mathScript = psserver->GetMathScriptEngine()->FindScript(name);
    if ( !mathScript )
    {
        Warning2(LOG_SPELLS, "Can't find math script for: %s", name.GetData() );
        return false;
    }

    manaScript = psserver->GetMathScriptEngine()->FindScript("CalculateManaCost");
    if ( !manaScript )
    {
        Warning1(LOG_SPELLS, "Can't find mana script!");
        return false;
    }

    castSuccessScript = psserver->GetMathScriptEngine()->FindScript("CalculateChanceOfCastSuccess");
    if ( !castSuccessScript )
    {
        Warning1(LOG_SPELLS, "Can't find cast success script!");
        return false;
    }

    varRange            = mathScript->GetOrCreateVar("Range");
    varMinRange         = mathScript->GetOrCreateVar("MinRange");
    varDuration         = mathScript->GetOrCreateVar("Duration");
    varPowerLevel       = mathScript->GetOrCreateVar("PowerLevel");
    varEffectRange      = mathScript->GetOrCreateVar("EffectRange");
    varEffectAngle      = mathScript->GetOrCreateVar("EffectAngle");
    varEffectTypes      = mathScript->GetOrCreateVar("EffectTypes");
    varProgressionDelay = mathScript->GetOrCreateVar("ProgressionDelay");
    varUseSaveThrow     = mathScript->GetOrCreateVar("UseSaveThrow");

    csString paramName;
    paramName.Format("Param%d", 0 );
    int x = 1;
    MathScriptVar* param = mathScript->GetVar(paramName);
    while ( param )
    {
        varParams.Push( param );
        paramName.Format("Param%d", x++ );
        param = mathScript->GetVar(paramName);
    }


    Result glyphs(db->Select("SELECT * from spell_glyphs WHERE spell_id=%d ORDER BY position ASC",id));
    if (glyphs.IsValid())
    {
        unsigned int i;
        for (i=0;i<glyphs.Count();i++)
        {
            psItemStats * stats = CacheManager::GetSingleton().GetBasicItemStatsByID(glyphs[i].GetInt("item_id"));
            if (stats)
            {
                glyphList.Push(stats);
            }
            else
            {
                Error3("Can't find item stats for item ID(%d) For Spell %s!", glyphs[i].GetInt("item_id"), name.GetData());
            }
        }
    }

    csString savingThrow = row["saving_throw"];
    if ( savingThrow.Length() > 0 )
    {
        saveThrow = new SavingThrow;
        // It is safe to do both since it will just set it to *_NONE
        saveThrow->skillSave = CacheManager::GetSingleton().ConvertSkillString( savingThrow );
        saveThrow->statSave = CacheManager::GetSingleton().ConvertAttributeString( savingThrow );
        saveThrow->value = row.GetInt("saving_throw_value");
    }

    return true;
}

float psSpell::ManaCost(float KFactor) const
{
    MathScriptVar *kVar = manaScript->GetVar("KFactor");
    MathScriptVar *realmVar = manaScript->GetVar("Realm");
    MathScriptVar *manaVar = manaScript->GetVar("ManaCost");

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

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

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

    kVar->SetValue((double)KFactor);
    realmVar->SetValue((double)realm);
    // Calculate the mana cost
    manaScript->Execute();
    // Done
    return (float)manaVar->GetValue();
}

float psSpell::ChanceOfSuccess(float KFactor, float waySkill, float relatedStat) const {
    MathScriptVar *kVar = castSuccessScript->GetVar("KFactor");
    MathScriptVar *realmVar = castSuccessScript->GetVar("Realm");
    MathScriptVar *waySkillVar = castSuccessScript->GetVar("WaySkill");
    MathScriptVar *relatedStatVar = castSuccessScript->GetVar("RelatedStat");
    MathScriptVar *chanceOfSuccess = castSuccessScript->GetVar("ChanceOfSuccess");

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

    if (!realmVar)
    {
        Warning1(LOG_SPELLS,"Couldn't find the Realm var in cast success script!\n");
        return 0.0f;
    }

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

    if (!relatedStatVar)
    {
        Warning1(LOG_SPELLS,"Couldn't find the RelatedStat var in cast success script!\n");
        return 0.0f;
    }

    if (!chanceOfSuccess)
    {
        Warning1(LOG_SPELLS,"Couldn't find the ChanceOfSuccess var in cast success script!\n");
        return 0.0f;
    }

    kVar->SetValue((double) KFactor);
    realmVar->SetValue((double) realm);
    waySkillVar->SetValue((double) waySkill);
    relatedStatVar->SetValue((double) relatedStat);

    castSuccessScript->Execute();
    return (float) chanceOfSuccess->GetValue();
}

PSSKILL psSpell::GetSkill() const
{
    return way->skill;
}

PSSKILL psSpell::GetRelatedStat() const
{
    return way->related_stat;
}

bool psSpell::MatchGlyphs(const glyphList_t & assembler)
{
    if (assembler.Length() != glyphList.Length()) return false;

    for (size_t i = 0; i < glyphList.Length(); i++)
    {
        if (assembler[i] != glyphList[i])
        {
            return false;
        }
    }
    return true;
}

psSpellGameEvent *psSpell::Cast(psSpellManager * mgr, Client * client, csString &effectName,
    csVector3 &offset, PS_ID &anchorID, PS_ID &targetID, unsigned int &castingDuration, csString *castingText) const
{
    gemActor *caster = client->GetActor();
    gemObject *target = client->GetTargetObject();
    int mode = caster->GetCharacterData()->GetMode();
   
    if (!target)
    {
        if ( spell_target & TARGET_SELF )
            target = caster;
        else
        {
            castingText->Format( "You must select a target for %s", name.GetData() );
            return NULL;
        }            
    }
    if (! caster->GetCharacterData()->CheckMagicKnowledge(GetSkill(), realm))
    {
        *castingText = "You have insufficient knowledge of this magic way to cast this spell.";
        return NULL;
    }

    if (mode != PSCHARACTER_MODE_PEACE  &&  mode != PSCHARACTER_MODE_COMBAT)
    {
        castingText->Format("You can't cast spells while %s.",caster->GetCharacterData()->GetModeStr());
        return NULL;
    }

    if (caster->GetCharacterData()->IsSpellCasting())
    {
        *castingText = "You are already casting a spell.";
        return NULL;
    }

    // Check if needed glyphs are available
    if (!caster->GetCharacterData()->HasGlyphs(glyphList))
    {
        castingText->Format("You don't have the purified glyphs to cast %s.",name.GetData());
        return NULL;
    }

    // Check for available Mana
    if (caster->GetCharacterData()->GetMana() < ManaCost(caster->GetCharacterData()->GetKFactor()))
    {
        castingText->Format("You don't have the mana to cast %s.",name.GetData());
        return NULL;
    }

    if (caster->GetCharacterData()->GetStamina(false) < ManaCost(caster->GetCharacterData()->GetKFactor()))
    {
        castingText->Format("You are too tired to cast %s.",name.GetData());
        return NULL;
    }
    
    if ((progression_event == "GainHP") &&
        (!( target->GetCharacterData()->GetHP() <  target->GetCharacterData()->GetHitPointsMax())))
    {
        castingText->Format("You cannot cast %s since %s is perfectly healthy!", name.GetData(), 
                             target->GetName());
        return NULL;
    }
    
    float powerLevel = MIN(max_power, caster->GetCharacterData()->GetPowerLevel(way->skill) );

    // Set Input variables to script
    varPowerLevel->SetValue(powerLevel);
    varUseSaveThrow->SetValue(0.0); // Unknown at this point. Will be set when script is executed
                                    // as part of the effect.
    mathScript->Execute();

    float max_range = varRange->GetValue();
    float min_range = varMinRange->GetValue();

    // If you can only cast it on yourself, do so.
    if ( max_range == -1 )
        target = caster;

    // Check the Target
    int target_type = client->GetTargetType( target );

    if (offensive && !client->IsAllowedToAttack(target,true))  // this function sends sys error msg
        return NULL;

    // Check if this spell needs a target and if so, it is the correct type
    if ( ( ( spell_target & TARGET_NONE ) == 0 ) && ( ( spell_target & target_type) == 0) )
    {
        csString targetTypeName;
        client->GetTargetTypeName( spell_target, targetTypeName );

        if(target)
            castingText->Format( "You cannot cast %s on %s. You can only cast it on %s.", 
                             name.GetData(), target->GetName(), targetTypeName.GetData());
        else
            castingText->Format( "You cannot cast %s . You can only cast it on %s.", 
                             name.GetData(), targetTypeName.GetData());
        return NULL;
    }
    
    if ( isTargetAffected( client, target, min_range, max_range, castingText ) )
    {
        // All conditions for casting this spell are met!
        // START CASTING SPELL
        caster->SetMode( PSCHARACTER_MODE_SPELL_CASTING );
        castingText->Format("You start casting spell %s",name.GetData());

        caster->DrainMana(-ManaCost(caster->GetCharacterData()->GetKFactor()),false);

        effectName = caster_effect;
        offset = csVector3(0,0,0);
        anchorID = caster->GetEntity()->GetID();
        targetID = target->GetEntity()->GetID();
        castingDuration = casting_duration;

        return new psSpellGameEvent( mgr, this, client, target, min_range, max_range, powerLevel);
    }

    return NULL;
}

bool psSpell::isTargetAffected( Client *client, gemObject *target, float min_range, float max_range, csString *castingText ) const
{
    gemActor *caster = client->GetActor();


    if (target)
    {
        // TODO: How to handle spell travel time?
        // Check for target within range

        if ( max_range == -1 && caster != target) // Self
        {
            castingText->Format("You can only cast %s on yourself.",name.GetData());
            return false;
        }
        else if (max_range == 0 && (caster->RangeTo(target) > SPELL_TOUCH_RANGE)) // Touch
        {
            castingText->Format("You are not in touch range of target %s to cast %s.",target->GetName(),name.GetData());
            return false;
        }
        else if (max_range > 0 && caster->RangeTo(target) > max_range)
        {
            castingText->Format("%s is too far away to cast %s.",target->GetName(),name.GetData() );
            return false;
        }
        else if (min_range > 0 && caster->RangeTo(target) < min_range)
        {
            castingText->Format("%s is too close to cast %s.",target->GetName(),name.GetData() );
            return false;
        }
    }

    return true;
}

int psSpell::checkRange( gemActor *caster, gemObject *target, float min_range, float max_range) const
{
    if (max_range == 0 && (caster->RangeTo(target) > SPELL_TOUCH_RANGE)) // Touch
    {
        return +1;
    }
    else if (max_range > 0 && caster->RangeTo(target) > max_range)
    {
        return +1;
    }
    else if (min_range > 0 && caster->RangeTo(target) < min_range)
    {
        return -1;
    }

    return 0;
}

csArray< gemObject *> *psSpell::getTargetsInRange(Client * client, float min_range, float max_range, float range) const
{
    gemActor *caster = client->GetActor();
    csArray< gemObject *> *targetsInRange  = caster->GetObjectsInRange( range );

    // Cycle through list and add any entities
    // that represent players to the proximity subscription list.

    csString reason;

    size_t i = 0;
    while ( i < targetsInRange->Length() )
    {
        gemObject *nearbyTarget = targetsInRange->Get( i );
        if (!nearbyTarget)
            continue;

        if ( ! isTargetAffected( client, nearbyTarget, min_range, max_range, &reason ) )
        {
            //CPrintf(CON_DEBUG,  reason );
            targetsInRange->DeleteIndex( i );
        }
        else
        {
            i++;
        }
    }
    return targetsInRange;
}

bool psSpell::Effect(psSpellManager * mgr, psSpellGameEvent * event, csString &effectName, csVector3 &offset, 
                     PS_ID &anchorID, PS_ID &targetID, csString *effectText) const
{
    gemActor * caster = event->caster->GetActor();
    gemObject * target = event->target;

    // Check for spell failure
    float chanceOfSuccess = ChanceOfSuccess(caster->GetCharacterData()->GetKFactor(), caster->GetCharacterData()->GetSkills()->GetSkillRank(GetSkill()), caster->GetCharacterData()->GetSkills()->GetSkillRank(GetRelatedStat()));

    Notify4(LOG_SPELLS, "%s Casting %s with a chance of success = %.2f\n",caster->GetName(), name.GetData(),chanceOfSuccess);

    if (psserver->GetRandom()*100.0 > chanceOfSuccess)
    {
        // Spell casting failed now we are in Peace mode again
        caster->SetMode( PSCHARACTER_MODE_PEACE );
        effectText->Format("You failed to cast spell %s",name.GetData());
        return false;
    }

    float max_range = event->max_range;
    float min_range = event->min_range;

    csString *castingText = new csString();
    // Handle Area of Effect Spells
    float effectRange = varEffectRange->GetValue();
    if (  ( effectRange > 0 ) ) //( (spell_target & TARGET_NONE) != 0 ) &&
    {
        csVector3 pos;
        float yrot;  // in radians
        iSector *sector;

        caster->GetPosition(pos,yrot,sector);

        // For a Cone area of Effect spell.
        float effectRadians = 0;
        if ( varEffectAngle )
        {
            effectRadians = varEffectAngle->GetValue(); // Angle in Degrees
        }
        if ( ( effectRadians <= 0 ) || ( effectRadians > 360 ) ) effectRadians = 360;
        effectRadians = ( effectRadians / 2 ) * ( PI / 180 ); // Convert Degrees to Radians, half on each side of the casters yrot
        CPrintf(CON_DEBUG, "Spell has an effect arc of %1.2f radians to either side of LOS.\n", effectRadians );

        // Get ProximityList
        csArray< gemObject *> *targetList = getTargetsInRange(event->caster,min_range, max_range, effectRange);

        if ( targetList->Length() == 0 )
        {
            CPrintf(CON_DEBUG, "No targets in range.\n");
            effectText->Format("You successfully cast spell %s, however there are no targets within its range.",name.GetData());
            return false;
        }

        // Loop through testing for rangedness
        for ( size_t i = 0; i < targetList->Length(); i ++)
        {
            gemObject *affectedTarget = targetList->Get( i );
            // if in range finish checks and cast

            csVector3 targetPos;
            float targetYrot;  // in radians
            iSector *targetSector;

            affectedTarget->GetPosition(targetPos,targetYrot,targetSector);

            targetYrot = atanf( ( targetPos.x - pos.x ) / ( targetPos.z - pos.z ) );
            if (targetYrot < 0 ) targetYrot = 2 * PI - targetYrot;

            if (effectRadians < PI * 2 )
            {
                float dX, dZ;
                dX = ( targetPos.x - pos.x);
                dZ = ( targetPos.z - pos.z);
                targetYrot = atanf( dZ / dX );
                // If targetYrot is -, convert to positive
                if (targetYrot < 0 ) targetYrot =  PI / 2 + targetYrot;
                // Based on the Quadrant the target is in, in relation to caster, adjust target angle
                // so that it is based the same as the casters yrot. (i.e. 0 is -Z axis, not -X)
                if ( ( dX < 0 ) && ( dZ < 0 ) ) // Quadrant 1 ( -Z to -X )
                {
                    targetYrot = PI/2 - targetYrot;
                }
                else if ( ( dX < 0 ) && ( dZ > 0 ) ) // Quadrant 2 ( -X to +Z )
                {
                    targetYrot = PI - targetYrot;
                }
                else if ( ( dX > 0 ) && ( dZ > 0 ) ) // Quadrant 3 ( +Z to +X )
                {
                    targetYrot = PI * 3 / 2 - targetYrot;
                }
                else if ( ( dX > 0 ) && ( dZ < 0 ) ) // Quadrant 4 ( +X to -Z )
                {
                    targetYrot = 2 * PI - targetYrot;
                }

                CPrintf(CON_DEBUG, "Target %s is %1.2f radians from LOS at a range of %1.2fm.\n", affectedTarget->GetName(), fabs( yrot - targetYrot ), caster->RangeTo(affectedTarget));
                if ( fabs( yrot - targetYrot ) < effectRadians )
                    CPrintf(CON_DEBUG, "Target %s is in affected area and is in range.\n", affectedTarget->GetName());
                else continue;
            }

            int target_type = event->caster->GetTargetType( affectedTarget );

            if ( ( target_type & spell_target) != 0 )
            {
                PS_ID affectedTargetID = affectedTarget->GetEntity()->GetID();
                event->target = affectedTarget;
                if (offensive)
                {
                    gemNPC *targetnpc = dynamic_cast<gemNPC *>(affectedTarget);
                    if (targetnpc)
                        psserver->npcmanager->QueueAttackPerception(caster, targetnpc);
                }
                return EffectTarget( event, effectName, offset, anchorID, affectedTargetID, effectText);
            }
        }
    }
    else // only one target
    {
        if ( target && isTargetAffected( event->caster, target, min_range, max_range, castingText ) )
        {
            if (offensive)
            {
                gemNPC *targetnpc = dynamic_cast<gemNPC *>(target);
                if (targetnpc)
                    psserver->npcmanager->QueueAttackPerception(caster, targetnpc);
            }
            return EffectTarget( event, effectName, offset, anchorID, targetID, effectText);
        }
        else
        {
            return false;
        }
    }
    return true;
}


bool psSpell::EffectTarget(psSpellGameEvent * event, csString &effectName, csVector3 &offset,
                           PS_ID& anchorID, PS_ID& targetID, csString *effectText) const
{
    ////////////////////////////////////
    // Begin Check for saving throw
    gemActor * caster = event->caster->GetActor();
    gemObject * target = event->target;

    // Check for spell failure
    bool saved = false;

    if ( saveThrow )
    {
        float random = psserver->GetRandom();
        int saveRolled = (int)(0.05 * (float)saveThrow->value );
        saveRolled +=(int)(random*(float)saveThrow->value);
        int playerAttrib = -1;

        psCharacter* targetData = target->GetCharacterData();
        if (targetData)
        {
            if ( saveThrow->statSave != PSITEMSTATS_STAT_NONE )
                playerAttrib = (int)targetData->GetAttributes()->GetStat( saveThrow->statSave );
            else if ( saveThrow->skillSave != PSSKILL_NONE )
                playerAttrib = (int)targetData->GetSkills()->GetSkillRank( saveThrow->skillSave );
        }
        if ( playerAttrib != -1.0 )
        {
            if ( saveRolled < playerAttrib )
                saved = true;
        }
    }

    // NBNB!!! Need to recalculate the global mathscript that might have been overriden
    //         by someone else that have casted this spell.
    // Set input variables before executing script
    if ( saved )
        varUseSaveThrow->SetValue(1);
    else
        varUseSaveThrow->SetValue(0);
    varPowerLevel->SetValue(event->powerLevel);
    mathScript->Execute();

    csTicks progression_delay = (csTicks)varProgressionDelay->GetValue();

    if (progression_delay)
    {
       psSpellEffectGameEvent *e = new psSpellEffectGameEvent(psserver->GetSpellManager(), event->spell, 
                                                              event->caster, event->target,
                                                              progression_delay, event->min_range,
                                                              event->max_range, saved, event->powerLevel);
       e->FireEvent();
    }
    else if (PerformResult(caster, target, event->min_range, event->max_range, saved, event->powerLevel) == false)
    {
        effectText->Format("%s has no effect.", name.GetData() );
        return false;
    }

/************************************************************
    // If this spell is one like: 15HP every 20Seconds
    if ( interval_time != 0 )
    {
        csString script;
        // How many times this effect runs.
        int turns = duration / interval_time;

        // A hack to place the <script> tags inside the <evt> tags. Need to
        // remove the existing <evt> tags first.
        psString eventStr( progEvent );
        psString what("<evt>");
        psString what2("</evt>");
        psString with(" " );
        eventStr.ReplaceSubString( what, with );
        eventStr.ReplaceSubString( what2, with );

        // Place that many turns into the progression queue.
        for ( int x = 1; x < turns; x++ )
        {
            script.Format( "<evt><script delay=\"%d\">%s</script></evt>", x*interval_time, eventStr.GetData() );
            psserver->GetProgressionManager()->ProcessScript( script,caster,target);
        }
    }
***************************************************/

    effectName = target_effect;
    offset = csVector3(0,0,0);
    anchorID = caster->GetEntity()->GetID();
    targetID = target->GetEntity()->GetID();

    return true;
}

bool psSpell::PerformResult(gemActor *caster, gemObject *target, float min_range, float max_range, bool saved, float powerLevel) const
{    
    switch ( checkRange(caster,target, min_range, max_range) )
    {
    case -1: // too close
        psserver->SendSystemInfo(caster->GetClientID(), "%s is too close.", target->GetName());
        return false;
        break;
    case 0: // in range
        break;
    case +1: // too far
        psserver->SendSystemInfo(caster->GetClientID(), "%s out of range.", target->GetName());
        return false;
        break;
    }

    // Get either the normal or saved event
    csString eventName;
    if (saved)
        eventName = saved_progression_event;
    else
        eventName = progression_event;

    // if no event is specified, do nothing
    if (eventName.Length() > 0)
    {
        ProgressionEvent *progEvent = psserver->GetProgressionManager()->FindEvent(eventName);
        if (progEvent)
        {
            // NBNB!!! Need to recalculate the global mathscript that might have been overriden
            //         by someone else that have casted this spell.
            // Set input variables before executing script
            if (saved)
                varUseSaveThrow->SetValue(1.0);
            else
                varUseSaveThrow->SetValue(0.0);
            varPowerLevel->SetValue(powerLevel);
            mathScript->Execute();

            progEvent->CopyVariables(mathScript);

            if ( progEvent->Run(caster,target,psserver->GetProgressionManager() ) )
            {
                return false;
            }
        }
        else
        {
            Error2("Failed to find progression event \"%s\"", eventName.GetData());
            return false;
        }
    }

    // Spell hit successfully.  Now let npcclient's know about it.
    psserver->GetNPCManager()->QueueSpellPerception(caster,target,npcSpellCategory,npcSpellCategoryID,npcSpellRelativePower);
    return true;
}

csString psSpell::SpellToXML() const
{
    csString glyphs;
    for (size_t i = 0; i < glyphList.Length(); i++)
    {
        if (i!=0) glyphs.Append(",");
        glyphs.Append(glyphList[i]->GetImageName());
    }
    csString xml;
    csString escpxml_name = EscpXML(name);
    csString escpxml_wayname = EscpXML(way->name);
    csString escpxml_glyphs = EscpXML(glyphs);
    xml.Format("<SPELL NAME=\"%s\" WAY=\"%s\" REALM=\"%d\" GLYPHS=\"%s\"/>",
               escpxml_name.GetData(), escpxml_wayname.GetData(),realm,escpxml_glyphs.GetData());
    return xml;
}

csString psSpell::DescriptionToXML() const
{
    csString xml;
    csString escpxml_name = EscpXML(name);
    csString escpxml_image = EscpXML(image);
    csString escpxml_description = EscpXML(description);
    xml.Format("<DESCRIPTION NAME=\"%s\" IMAGE=\"%s\" DESC=\"%s\" />",
               escpxml_name.GetData(), escpxml_image.GetData(), escpxml_description.GetData());
    return xml;
}

bool psSpell::operator==(const psSpell& other) const
{
    return id == other.id;
}

bool psSpell::operator<(const psSpell& other) const
{
    return id < other.id;
}
