/*
 * combatmanager.cpp
 *
 * Coptright (C) 2001-2002 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 <csutil/xmltiny.h>

#include <physicallayer/entity.h>
#include <propclass/mesh.h>

#include "psserver.h"
#include "playergroup.h"
#include "net/msghandler.h"
#include "net/messages.h"
#include "events.h"
#include "gem.h"
#include "entitymanager.h"
#include "util/eventmanager.h"
#include "util/location.h"
#include "util/mathscript.h"
#include "util/serverconsole.h"
#include "psproxlist.h"
#include "spawnmanager.h"
#include "progressionmanager.h"
#include "groupmanager.h"
#include "npcmanager.h"
#include "combatmanager.h"
#include "globals.h"
#include "bulkobjects/psitem.h"
#include "netmanager.h"

/// This #define determines how far away people will get detailed combat events.
#define MAX_COMBAT_EVENT_RANGE 30

/// This is the initial stance when attacked
#define STANCE_WHEN_ATTACKED  ((PSCHARACTER_STANCE)3)


/**
 * When a player or NPC attacks someone, the first combat is queued.  When
 * that event is processed, if appropriate at the end, the next combat event
 * is immediately created and added to the schedule to be triggered at the
 * appropriate time.
 *
 * Note that this event is just a hint that a combat event might occur at this
 * time.  Other events (like equiping items, removing items, spell effects, etc)
 * may alter this time.
 *
 * When a combat event fires, the first task is to check wether the action can actually
 * occur at this time.  If it cannot, the event should not create another event since
 * the action that caused the "delay" or "acceleration" change should have created its 
 * own event.
 *
 * TODO: psGEMEvent makes this depend only on the attacker when in fact
 * this event depends on both attacker and target being present at the time the
 * event fires.
 */
class psCombatGameEvent : public psGameEvent
{
protected:
    psCombatManager *combatmanager;
    int action;

public:

    csRef<gemObjectSafe>  attacker;  ///< Entity who instigated this attack
    csRef<gemObjectSafe>  target;    ///< Entity who is target of this attack
    psCharacter *attackerdata;
    psCharacter *targetdata;
    int TargetCID;                   ///< ClientID of target
    int AttackerCID;                 ///< ClientID of attacker
    int WeaponSlot;                  ///< Identifier of the slot for which this attack event should process
    uint32 WeaponID;                 ///< UID of the weapon used for this attack event
    float AttackValue;               ///< Measure of quality of attack ability
    float AttackRoll;                ///< Randomized attack value.  Used for
    float DefenseRoll;               ///< Randomized defense effectiveness this event
    float DodgeValue;                ///< Measure of quality of dodge ability of target
    float BlockValue;                ///< Quality of blocking ability of all weapons target has
    float CounterBlockValue;         ///< Dificulty of blocking attacking weapon
    float QualityOfHit;              ///< How much of attack remains after shield protection
    float BaseHitDamage;             ///< Hit damage without taking armor into account.
    int   AttackLocation;            ///< Which slot should we check the armor of?
    float ArmorDamageAdjustment;     ///< How much does armor in the struck spot help?
    float FinalBaseDamage;           ///< Resulting damage after armor adjustment
    float DamageMods;                ///< Magnifiers on damage for magic effects, etc.
    float FinalDamage;               ///< Final damage applied to target

    int   AttackResult;              ///< Code indicating the result of the attack attempt
    int   PreviousAttackResult;      ///< The code of the previous result of the attack attempt

    psCombatGameEvent(psCombatManager *mgr,
                      int delayticks,
                      int action,
                      gemObject *attacker,
                      int weaponslot,
                      uint32 weaponID,
                      gemObject *target,
                      int attackerCID,
                      int targetCID,
                      int previousResult);
    ~psCombatGameEvent();                      

    virtual bool CheckTrigger();
    virtual void Trigger();  // Abstract event processing function

    gemObjectSafe *GetTarget()
    {
        return target;
    };
    gemObjectSafe *GetAttacker()
    {
        return attacker;
    };
    psCharacter *GetTargetData()
    {
        return targetdata;
    };
    psCharacter *GetAttackerData()
    {
        return attackerdata;
    };
    int GetWeaponSlot()
    {
        return WeaponSlot;
    };
    
//    virtual void Disconnecting(void * object);
    
    int GetTargetID()               { return TargetCID; };
    int GetAttackerID()             { return AttackerCID; };
    int GetAttackResult()           { return AttackResult; };
};



const char *psCombatManager::stance_name[] = { "dummy","bloody","aggressive","normal","defensive","full defensive" };


psCombatManager::psCombatManager(EntityManager *celserv)
{
    entitymanager = celserv;

    randomgen = psserver->rng;
    var_IAH       = NULL;         
    var_AHR       = NULL;         
    var_Blocked   = NULL;
    var_QOH       = NULL;
    var_FinalDmg  = NULL;
  
    InitializePVP();

    script_engine = psserver->GetMathScriptEngine();
    calc_damage   = script_engine->FindScript("Calculate Damage");
    if ( !calc_damage )
    {
        Error1("Calculate Damage Script could not be found. Check rpgrules.xml");            
    }
    else
    {
        // Output var bindings here
        var_IAH       = calc_damage->GetVar("IAH");
        var_AHR       = calc_damage->GetVar("AHR");
        var_Blocked   = calc_damage->GetVar("Blocked");
        var_QOH       = calc_damage->GetVar("QOH");
        var_FinalDmg  = calc_damage->GetVar("FinalDamage");
  
        if ( !(var_IAH && var_AHR && var_Blocked && var_QOH && var_FinalDmg) )
        {
            Error1("One or more combat output binding variables is incorrect");
            Error2("    IAH = %p", var_IAH);
            Error2("    AHR = %p", var_AHR);
            Error2("    Blocked = %p", var_Blocked);
            Error2("    QOH = %p", var_QOH);
            Error2("    FinalDamage = %p", var_FinalDmg);
            Error1("Check rpgrules.xml to make sure these variables exist in 'Calculate Damage' script");
        } 

        // Input var bindings here
        var_AttackWeapon       = calc_damage->GetVar("AttackWeapon");
        //var_AttackWeaponSecondary  = calc_damage->GetVar("AttackWeaponSecondary"); TODO
        var_TargetAttackWeapon      = calc_damage->GetVar("TargetAttackWeapon");
        //var_DefenseWeaponSecondary = calc_damage->GetVar("DefenseWeaponSecondary"); TODO
        var_Target                 = calc_damage->GetVar("Target");
        var_Attacker               = calc_damage->GetVar("Attacker");
        var_AttackLocationItem     = calc_damage->GetVar("AttackLocationItem");

        targetLocations.Push(PSCHARACTER_SLOT_HEAD);
        targetLocations.Push(PSCHARACTER_SLOT_TORSO);
        targetLocations.Push(PSCHARACTER_SLOT_ARMS);
        targetLocations.Push(PSCHARACTER_SLOT_GLOVES);
        targetLocations.Push(PSCHARACTER_SLOT_LEGS);
        targetLocations.Push(PSCHARACTER_SLOT_BOOTS);
    } 

    psserver->GetEventManager()->Subscribe(this,MSGTYPE_DEATH_EVENT,NO_VALIDATION);
}

psCombatManager::~psCombatManager()
{
    psserver->GetEventManager()->Unsubscribe(this,MSGTYPE_DEATH_EVENT);
}

void psCombatManager::InitializePVP()
{
    csRef<iVFS> vfs;
    vfs = CS_QUERY_REGISTRY( psserver->GetObjectReg(), iVFS );

    csRef<iDocumentSystem> xml = csPtr<iDocumentSystem>(new csTinyDocumentSystem);

    csRef<iDataBuffer> buff = vfs->ReadFile( "/planeshift/data/pvp_regions.xml" );

    if ( !buff || !buff->GetSize() )
    {
        return;
    }

    csRef<iDocument> doc = xml->CreateDocument();
    const char* error = doc->Parse( buff );
    if ( error )
    {
        Error2("Parse error %s", error);
        return;
    }
    csRef<iDocumentNode> root    = doc->GetRoot();
    if(!root)
    {
        Error1("No XML root in pvp_regions.xml");
        return;
    }
    csRef<iDocumentNode> topNode = root->GetNode("pvp_regions");
    if(!topNode)
    {
        Error1("No <pvp_regions> in pvp_regions.xml");
        return;
    }
    csRef<iDocumentNodeIterator> iter = topNode->GetNodes();

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

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

        // This is a widget so read it's factory to create it.
        if ( strcmp( node->GetValue(), "region" ) == 0)
        {
            LocationType *loctype = new LocationType;
            loctype->region = true;

            if (loctype->Load(node))
            {
                pvp_regions.Push(loctype);
            }
            else
            {
                delete loctype;
                return;
            }
        }
    }
}

bool psCombatManager::InPVPRegion(csVector3& pos)
{
    size_t i;
    for (i=0; i<pvp_regions.Length(); i++)
    {
        if (pvp_regions[i]->CheckWithinBounds(pos))
            return true;
    }
    return false;
}

void psCombatManager::AttackSomeone(gemActor *attacker,gemObject *target,int stance)
{
    psCharacter *attacker_character = attacker->GetCharacterData();

    if (attacker_character->GetMode() == PSCHARACTER_MODE_COMBAT)  // Already fighting
    {
        SetCombat(attacker,stance);  // switch stance from Bloody to Defensive, etc.
        return;
    }

    // Indicator of whether any weapons are available to attack with
    bool StartedAttacking=false;

    // Step through each current slot and queue events for all that can attack
    for (int slot=0;slot<PSCHARACTER_SLOT_COUNT;slot++)
    {
        // See if this slot is able to attack
        if (attacker_character->Inventory().Attackable(slot))
        {
            // Get the data for the "weapon" that is used in this slot
            psItem *Weapon=attacker_character->Inventory().GetEffectiveWeaponInSlot(slot);
            if (Weapon!=NULL)
            {
                QueueNextEvent(attacker,slot,target,attacker->GetClientID(),target->GetClientID());  // start the ball rolling
                StartedAttacking=true;
            }
        }
    }

    /* Only notify the target if any attacks were able to start.  Otherwise there are
     * no available weapons with which to attack.
     */
    if (StartedAttacking)
    {
        // The attacker should now enter combat mode
        if (attacker->GetMode() != PSCHARACTER_MODE_COMBAT)
            SetCombat(attacker,stance);

        NotifyTarget(attacker,target);
    }
    else
    {
        psserver->SendSystemError(attacker->GetClientID(),"You have no weapons equipped!");
        return;
    }
}

void psCombatManager::SetCombat(gemActor *combatant, int stance)
{
    // Sanity check
    if (!combatant || !combatant->GetCharacterData() || !combatant->IsAlive())
        return;

    // Change stance if new and for a player (NPCs don't have different stances)
    if (combatant->GetClientID() && combatant->GetCharacterData()->GetCombatStance() != stance)
    {
        psSystemMessage msg(combatant->GetClientID(), MSG_COMBAT_STANCE, "%s changes to a %s stance", combatant->GetName(), stance_name[stance] );
        msg.Multicast(combatant->GetMulticastClients(),0,MAX_COMBAT_EVENT_RANGE);

        combatant->GetCharacterData()->SetCombatStance((PSCHARACTER_STANCE)stance);
    }

    combatant->SetMode(PSCHARACTER_MODE_COMBAT); // Set mode and multicast new mode and/or stance
    Debug3(LOG_COMBAT,combatant->GetClientID(), "%s starts attacking with stance %d", combatant->GetName(), stance);
}

void psCombatManager::StopAttack(gemActor *attacker)
{
    // Sanity check
    if(!attacker || !attacker->IsAlive())
        return;

    attacker->SetMode(PSCHARACTER_MODE_PEACE);
    Debug2(LOG_COMBAT, attacker->GetClientID(), "%s stops attacking", attacker->GetName());
}

void psCombatManager::NotifyTarget(gemActor *attacker,gemObject *target)
{
    // Queue Attack percetion to npc clients
    gemNPC *targetnpc = dynamic_cast<gemNPC *>(target);
    if (targetnpc)
        psserver->npcmanager->QueueAttackPerception(attacker,targetnpc);

    // Interrupt spell casting
    //gemActor *targetactor = dynamic_cast<gemActor*>(target);
    //if (targetactor)
    //    targetactor->GetCharacterData()->InterruptSpellCasting();
}

void psCombatManager::QueueNextEvent(psCombatGameEvent *event)
{
    QueueNextEvent(event->GetAttacker()->gemobj,
                   event->GetWeaponSlot(),
                   event->GetTarget()->gemobj,
                   event->GetAttackerID(),
                   event->GetTargetID(),
                   event->GetAttackResult());
}

void psCombatManager::QueueNextEvent(gemObject *attacker,int weaponslot,
                                     gemObject *target,
                                     int attackerCID,
                                     int targetCID, int previousResult)
{

    /* We should check the combat queue here if a viable combat queue method is
     *  ever presented.  As things stand the combat queue is not workable with 
     *  multiple weapons being used.
     */
// Get next action from QueueOfAttacks
//    int action = GetQueuedAction(attacker);

// If no next action, create default action based on mode.
//    if (!action)
//    {
//        action = GetDefaultModeAction(attacker);
//    }

    int action=0;

    psCharacter *Character=attacker->GetCharacterData();
    psItem *Weapon=Character->Inventory().GetEffectiveWeaponInSlot(weaponslot);
    uint32 weaponID = Weapon->GetUID();

    float latency = Weapon->GetLatency();
    int delay = (int)(latency*1000);

    // Create first Combat Event and queue it.
    psCombatGameEvent *event;
    
    event = new psCombatGameEvent(this,
                                  delay,
                                  action,
                                  attacker,
                                  weaponslot,
                                  weaponID,
                                  target,
                                  attackerCID,
                                  targetCID,
                                  previousResult);

    psserver->GetEventManager()->Push(event);
}

/* ----------------------- NOT IMPLEMENTED YET -------------------------
int psCombatManager::GetQueuedAction(gemActor *attacker)
{
    (void) attacker;
    // TODO: This will eventually query the prop class for the next action
    // in the queue.

    return 0;
}

int psCombatManager::GetDefaultModeAction(gemActor *attacker)
{
    (void) attacker;
    // TODO: This will eventually query the prop class for the mode
    // if nothing is in the queue.

    return 1;
}*/


/**
 * This is the meat and potatoes of the combat engine here.
 */
int psCombatManager::CalculateAttack(psCombatGameEvent *event)
{
    var_Attacker->SetObject(event->GetAttackerData() );
    var_Target->SetObject(event->GetTargetData() );

    psItem* item = event->GetAttackerData()->Inventory().GetEffectiveWeaponInSlot(event->GetWeaponSlot() );

    if (var_AttackWeapon)
        var_AttackWeapon->SetObject(event->GetAttackerData()->Inventory().GetEffectiveWeaponInSlot(event->GetWeaponSlot() ) );

    if (var_TargetAttackWeapon)
        var_TargetAttackWeapon->SetObject(event->GetTargetData()->Inventory().GetEffectiveWeaponInSlot(event->GetWeaponSlot() ) );

    if (var_AttackLocationItem)
    {
        int idx = randomgen->Get((int)targetLocations.Length());        
        int attack_location = targetLocations[idx];
        var_AttackLocationItem->SetObject(event->GetTargetData()->Inventory().GetEffectiveArmorInSlot(attack_location) );
        event->AttackLocation = attack_location;
    }

    calc_damage->Execute();

    //calc_damage->DumpAllVars();

    if (var_IAH->GetValue() < 0.0F)
        return ATTACK_MISSED;

    if (var_AHR->GetValue() < 0.0F)
        return ATTACK_DODGED;

    if (var_Blocked->GetValue() < 0.0F)
       return ATTACK_BLOCKED;

    event->FinalDamage=var_FinalDmg->GetValue();

    return ATTACK_DAMAGE;
}

void psCombatManager::ApplyCombatEvent(psCombatGameEvent *event, int attack_result)
{
    psCharacter *attacker_data,*target_data;

    attacker_data=event->GetAttackerData();
    target_data=event->GetTargetData();

    psItem *weapon         = attacker_data->Inventory().GetEffectiveWeaponInSlot(event->GetWeaponSlot());
    psItem *blockingWeapon = target_data->Inventory().GetEffectiveWeaponInSlot(event->GetWeaponSlot());
    psItem *struckArmor    = target_data->Inventory().GetEffectiveArmorInSlot(event->AttackLocation);

    // if no armor, then ArmorVsWeapon = 1
    float ArmorVsWeapon = 1;
    if (struckArmor)
      ArmorVsWeapon = weapon->GetArmorVSWeaponResistance(struckArmor->GetBaseStats());
    // clamp values due to bad data
    ArmorVsWeapon = ArmorVsWeapon > 1.0F ? 1.0F : ArmorVsWeapon;
    ArmorVsWeapon = ArmorVsWeapon < 0.0F ? 0.0F : ArmorVsWeapon;

    gemActor *gemAttacker = dynamic_cast<gemActor*> (event->attacker->gemobj);
    gemActor *gemTarget   = dynamic_cast<gemActor*> (event->target->gemobj);

    switch (attack_result)
    {
        case ATTACK_DAMAGE:
        {
            bool isNearlyDead = false;
            if (target_data->GetHitPointsMax() > 0.0 && target_data->GetHP()/target_data->GetHitPointsMax() > 0.2)
            {
                if ((target_data->GetHP() - event->FinalDamage) / target_data->GetHitPointsMax() <= 0.2)
                    isNearlyDead = true;
            }

            psCombatEventMessage ev(event->AttackerCID,
                isNearlyDead ? psCombatEventMessage::COMBAT_DAMAGE_NEARLY_DEAD : psCombatEventMessage::COMBAT_DAMAGE,
                gemAttacker->GetEntity()->GetID(),
                gemTarget->GetEntity()->GetID(),
                event->AttackLocation,
                event->FinalDamage,
                weapon->GetAttackAnimID(gemAttacker->GetCharacterData(),gemAttacker->GetEntity() ),
                gemTarget->FindAnimIndex("hit"));

            ev.Multicast(gemTarget->GetMulticastClients(),0,MAX_COMBAT_EVENT_RANGE);

            // Apply final damage
            if (target_data!=NULL)
            {
                gemTarget->DoDamage(gemAttacker,event->FinalDamage);
                
                if (gemAttacker)
                    gemAttacker->InvokeAttackScripts(gemTarget);

                if (gemTarget)
                    gemTarget->InvokeDamageScripts(gemAttacker);

                if (gemTarget->GetClientID() == 0 && !gemTarget->GetCharacterData()->IsPet())
                {
                    // Successful attack of NPC, train skill.
                    Debug1(LOG_COMBAT, gemAttacker->GetClientID(), "Training Weapon Skills On Attack\n");
                    gemAttacker->GetCharacterData()->PracticeWeaponSkills(weapon,1);
                }
            }
            
            // If the target wasn't in combat, it is now...
            if (gemTarget->GetMode() != PSCHARACTER_MODE_COMBAT)
            {
                if (gemTarget->GetClient())  // Set reciprocal target
                    gemTarget->GetClient()->SetTargetObject(gemAttacker,true);

                AttackSomeone(gemTarget,gemAttacker,STANCE_WHEN_ATTACKED);
            }

            if (weapon)
                weapon->AddDecay(1.0F - ArmorVsWeapon);
            if (struckArmor)
                struckArmor->AddDecay(ArmorVsWeapon);

            break;
        }
        case ATTACK_DODGED:
        {
            psCombatEventMessage ev(event->AttackerCID,
                psCombatEventMessage::COMBAT_DODGE,
                gemAttacker->GetEntity()->GetID(),
                gemTarget->GetEntity()->GetID(),
                event->AttackLocation,
                0, // no dmg on a dodge
                weapon->GetAttackAnimID( gemAttacker->GetCharacterData(),gemAttacker->GetEntity() ),
                (unsigned int)-1); // no defense anims yet

            ev.Multicast(gemTarget->GetMulticastClients(),0,MAX_COMBAT_EVENT_RANGE);

            if (gemAttacker->GetClientID() == 0 && !gemAttacker->GetCharacterData()->IsPet())
            {
                // Successful dodged by target, train skill.
                Debug1(LOG_COMBAT, gemAttacker->GetClientID(), "Training Armour Skills On Dodge\n");
                gemTarget->GetCharacterData()->PracticeArmorSkills(1, event->AttackLocation);
            }
            break;
        }
        case ATTACK_BLOCKED:
        {
            psCombatEventMessage ev(event->AttackerCID,
                psCombatEventMessage::COMBAT_BLOCK,
                gemAttacker->GetEntity()->GetID(),
                gemTarget->GetEntity()->GetID(),
                event->AttackLocation,
                0, // no dmg on a block
                weapon->GetAttackAnimID( gemAttacker->GetCharacterData(),gemAttacker->GetEntity() ),
                (unsigned int)-1); // no defense anims yet

            ev.Multicast(gemTarget->GetMulticastClients(),0,MAX_COMBAT_EVENT_RANGE);

            if (gemAttacker->GetClientID() == 0 && !gemAttacker->GetCharacterData()->IsPet())
            {
                // Successful blocked by target, train skill.
                Debug1(LOG_COMBAT, gemAttacker->GetClientID(), "Training Armour Skills On Block\n");
                gemTarget->GetCharacterData()->PracticeArmorSkills(1, event->AttackLocation);
            }

            if (weapon)
                weapon->AddDecay(ITEM_DECAY_FACTOR_BLOCKED);  
            if (blockingWeapon)
                blockingWeapon->AddDecay(ITEM_DECAY_FACTOR_PARRY);

            break;
        }
        case ATTACK_MISSED:
        {
            psCombatEventMessage ev(event->AttackerCID,
                psCombatEventMessage::COMBAT_MISS,
                gemAttacker->GetEntity()->GetID(),
                gemTarget->GetEntity()->GetID(),
                event->AttackLocation,
                0, // no dmg on a block
                weapon->GetAttackAnimID( gemAttacker->GetCharacterData(),gemAttacker->GetEntity() ),
                (unsigned int)-1); // no defense anims yet

            ev.Multicast(gemTarget->GetMulticastClients(),0,MAX_COMBAT_EVENT_RANGE);
            break;
        }
        case ATTACK_OUTOFRANGE:
        {
            if (event->AttackerCID)
            {
                psserver->SendSystemError(event->AttackerCID,"You are too far away to attack!");
                if (event->attacker && event->attacker.IsValid())
                    StopAttack(dynamic_cast<gemActor*>(event->attacker->gemobj));  // if you run away, you exit attack mode
            }
            break;
        }
    }

    // Notify that the attack took place
    attacker_data->NotifyAttackPerformed(event->GetWeaponSlot(),csGetTicks());
}

void psCombatManager::HandleCombatEvent(psCombatGameEvent *event)
{
    psCharacter *attacker_data,*target_data;
    int attack_result;
    bool skipThisRound = false;

    if (!event->GetAttacker()->gemobj || !event->GetTarget()->gemobj) // disconnected and deleted
        return;

    gemActor *gemAttacker = dynamic_cast<gemActor*> (event->attacker->gemobj);
    gemActor *gemTarget   = dynamic_cast<gemActor*> (event->target->gemobj);

    attacker_data=event->GetAttackerData();
    target_data=event->GetTargetData();

    // If the attacker is no longer in attack mode or target is dead, abort.
    if (attacker_data->GetMode() != PSCHARACTER_MODE_COMBAT || !gemTarget->IsAlive() )
        return;
    
    // If the slot is no longer attackable, abort
    if (!attacker_data->Inventory().Attackable(event->GetWeaponSlot()))
        return;

    // If the slot next attack time is not yet up, abort (another event sequence should have been started)
    if (attacker_data->GetSlotNextAttackTime(event->GetWeaponSlot()) > csGetTicks())
        return;

    psItem* weapon = attacker_data->Inventory().GetEffectiveWeaponInSlot(event->GetWeaponSlot());

    // If the weapon in the slot has been changed, skip a turn (latency for this slot may also have changed)
    if (event->WeaponID != weapon->GetUID())
    {
        Debug2(LOG_COMBAT, gemAttacker->GetClientID(),"%s has changed weapons mid battle", gemAttacker->GetName() );
        skipThisRound = true;
    }

    Client * attacker_client = psserver->GetNetManager()->GetClient(event->AttackerCID);
    if (attacker_client)
    {
        // if the player is too tired, stop at a % based on stance
        float tired = (attacker_client->GetCharacterData()->GetCombatStance() -1) * .1;
        if ( (attacker_client->GetCharacterData()->GetStamina(true) < (attacker_client->GetCharacterData()->GetStaminaMax(true)*tired))
            || (attacker_client->GetCharacterData()->GetStamina(false) < (attacker_client->GetCharacterData()->GetStaminaMax(false)*tired)) )
        {
           StopAttack(attacker_data->GetActor());
           psserver->SendSystemError(event->AttackerCID, "You are too tired to attack.");
           return;
        }

        // If the target has become impervious, abort and give up attacking
        if (!attacker_client->IsAllowedToAttack(gemTarget))
        {
           StopAttack(attacker_data->GetActor());
           return;
        }

        // If the target has changed, abort (assume another combat event has started since we are still in attack mode)
        if (gemTarget != attacker_client->GetTargetObject())
            return;
    }
    else
    {
        // Check if the npc's target has changed (if it has, then assume another combat event has started.)
        gemNPC* npcAttacker = dynamic_cast<gemNPC*>(gemAttacker);
        if (npcAttacker && npcAttacker->GetTarget() != gemTarget)
            return;
    }

    if (attacker_data->IsSpellCasting())
    {
        psserver->SendSystemInfo(event->AttackerCID, "You can't attack while casting spells.");
        skipThisRound = true;
    }

    if (!skipThisRound)
    {
        // If the target is out of range, skip this attack round, send a warning
        if ( !ValidateDistance(gemAttacker, gemTarget, weapon) )
        {
            // Attacker is a npc so does not realise it's attack is cancelled
            attack_result=ATTACK_OUTOFRANGE;

        }
        else
        {
            // If we didn't attack last time target might have forgotten about us by now
            // so we should remind him.
            if(event->PreviousAttackResult==ATTACK_OUTOFRANGE)
                NotifyTarget(gemAttacker,gemTarget);
            attack_result=CalculateAttack(event);
        }

        event->AttackResult=attack_result;

        //#ifdef DEBUGCOMBAT
            DebugOutput(event);
        //#endif

        ApplyCombatEvent(event, attack_result);
    }

    // Queue next event to continue combat if this is an auto attack slot
    if (attacker_data->Inventory().AutoAttackable(event->GetWeaponSlot()))
    {
//      CPrintf(CON_DEBUG, "Queueing Slot %d for %s's next combat event.\n",event->GetWeaponSlot(), event->attacker->GetName() );
        QueueNextEvent(event);
    }
//    else
//        CPrintf(CON_DEBUG, "Slot %d for %s not an auto-attack slot.\n",event->GetWeaponSlot(), event->attacker->GetName() );
}

void psCombatManager::DebugOutput(psCombatGameEvent *event)
{
    psItem* item = event->GetAttackerData()->Inventory().GetEffectiveWeaponInSlot(event->GetWeaponSlot() );
    psString debug;
    debug.Append( "-----Debug Combat Summary--------\n");
    debug.AppendFmt( "%s attacks %s with slot %d , weapon %s, quality %1.2f, basedmg %1.2f/%1.2f/%1.2f\n",
      event->attacker->gemobj->GetName(),event->target->gemobj->GetName(), event->GetWeaponSlot(),item->GetName(),item->GetItemQuality(),
      item->GetDamage(PSITEMSTATS_DAMAGETYPE_SLASH),item->GetDamage(PSITEMSTATS_DAMAGETYPE_BLUNT),item->GetDamage(PSITEMSTATS_DAMAGETYPE_PIERCE));
    debug.AppendFmt( "IAH: %1.6f AHR: %1.6f Blocked: %1.6f",var_IAH->GetValue(),var_AHR->GetValue(),var_Blocked->GetValue());
    debug.AppendFmt( "QOH: %1.6f Damage: %1.1f\n",var_QOH->GetValue(),var_FinalDmg->GetValue());
    Debug1(LOG_COMBAT, event->attacker->gemobj->GetClientID(),debug.GetData());

}





bool psCombatManager::ValidateDistance(gemObject *attacker,gemObject *target,psItem *Weapon)
{
    if (Weapon==NULL)
        return false;

    if (!Weapon->GetIsRangeWeapon())
    {
        if(!attacker->GetNPCPtr())
            return attacker->IsNear(target,2.0F);  // within 2.0m for melee - for PCs
        else
            return attacker->IsNear(target,3.0F);  // within 3.0m for melee - for NPCs
    }
    else
        return false; // Range weapons to be added later
   
}

void psCombatManager::HandleMessage(MsgEntry *me,Client *client)
{
    if (me->GetType() == MSGTYPE_DEATH_EVENT)
    {
        HandleDeathEvent(me);
        return;
    }
}

void psCombatManager::HandleDeathEvent(MsgEntry *me)
{
    psDeathEvent death(me);

    Debug1(LOG_COMBAT,death.deadActor->GetClientID(),"Combat Manager handling Death Event\n");

    // Stop actor moving.
    death.deadActor->StopMoving();

    // Send out the notification of death, which plays the anim, etc.
    psCombatEventMessage die(death.deadActor->GetClientID(),
                                psCombatEventMessage::COMBAT_DEATH,
                                (death.killer)?death.killer->GetEntity()->GetID():0,
                                death.deadActor->GetEntity()->GetID(),
                                -1, // no target location
                                0,  // no dmg on a death
                                (unsigned int)-1,  // TODO: "killing blow" matrix of mob-types vs. weapon types
                                (unsigned int)-1 ); // Death anim on client side is handled by the death mode message

    die.Multicast(death.deadActor->GetMulticastClients(),0,MAX_COMBAT_EVENT_RANGE);
}


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



psCombatGameEvent::psCombatGameEvent(psCombatManager *mgr,
                                     int delayticks,
                                     int act,
                                     gemObject *attacker,
                                     int weaponslot,
                                     uint32 weapon,
                                     gemObject *target,
                                     int attackerCID,
                                     int targetCID,
                                     int previousResult)
  : psGameEvent(0,delayticks,"psCombatGameEvent")
{
    combatmanager  = mgr;
    action         = act;
    this->attacker = attacker->GetSafeReference();
    this->WeaponSlot = weaponslot;
    this->WeaponID = weapon;
    this->target   = target->GetSafeReference();
    this->AttackerCID = attackerCID;
    this->TargetCID   = targetCID;

    // Also register the target as a disconnector
//    target->Register(this);
//    attacker->Register( this ); 
    
    attackerdata = attacker->GetCharacterData();
    targetdata   = target->GetCharacterData();

    if (!attackerdata || !targetdata)
        return;

    AttackValue=-1;      
    AttackRoll=-1;
    DefenseRoll=-1;
    DodgeValue=-1;
    BlockValue=-1;       
    QualityOfHit=-1;     
    BaseHitDamage=-1;    
    AttackLocation=-1;   
    ArmorDamageAdjustment=-1;
    FinalBaseDamage=-1;  
    DamageMods=-1;       
    FinalDamage=-1;
    AttackResult=ATTACK_NOTCALCULATED;
    PreviousAttackResult=previousResult;
}


psCombatGameEvent::~psCombatGameEvent()
{
//    if (!valid)
//        return;

/***
    if ( target && target->IsValid() )
    {        
        target->Unregister(this);
        target = NULL;
    }        
    if ( attacker && attacker->IsValid() )
    {
        attacker->Unregister(this);
        target = NULL;
    }
 ***/
}

bool psCombatGameEvent::CheckTrigger()
{
    if ( attacker && attacker->gemobj && target && target->gemobj)
    {
        if ( attacker->gemobj->IsAlive() && target->gemobj->IsAlive() )
        {
            return true;
        }
        else
        {
            return false;
        }                    
    }        
    else
    {
        return false;
    }        
}

void psCombatGameEvent::Trigger()
{
    if (!attacker || !attacker->gemobj || !target || !target->gemobj)
        return;

    combatmanager->HandleCombatEvent(this);
}

/************
void psCombatGameEvent::Disconnecting(void * object)
{
    psGEMEvent::Disconnecting(object);
    valid = false;
    if (attacker)
    {
        attacker->Unregister(this);
        attacker = NULL;
    }
    if ( target )
    {
        target->Unregister(this);
        target = NULL;
    }
}
*****/
