/******************************************************************
  
  Module:  rule_global.cpp
  
  Author: Sean Craig
  
  Copyright 2005 Sony Online Entertainment.  All rights reserved.
  
*******************************************************************/

//-------------------------------------------------------- Includes
#include "rule_global.h"
#include "rule.h"
#include "titan.h"
#include "stats.h"
#include "registry.h"
#include "graphic.h"
#include "animcontroller.h"
#include "tuning.h"
#include "database.h"
#include "inventory.h"
#include "spell.h"


static float l_BLOCK_REGEN = 30.0f;

//---------------------------------------------- Class Declarations
class cGlobalCombatRule : public cRule
{
public:
  virtual const char * GetName(){return "Global Character";};

  virtual void    CalcHealthMax(cCalcPacket *packet);
  virtual void    CalcHealthRegen(cCalcPacket *packet);
  virtual void    CalcManaMax(cCalcPacket *packet);
  virtual void    CalcManaRegen(cCalcPacket *packet);
  virtual void    CalcBlockMax(cCalcPacket *packet);
  virtual void    CalcBlockRegen(cCalcPacket *packet);

  virtual void    CalcStrikethrough(cDamagePacket *packet);
  virtual void    CalcDamageInitial(cDamagePacket *packet);
  virtual void    CalcDamageRaw(cDamagePacket *packet);
  virtual void    CalcDamageReduced(cDamagePacket *packet);

  virtual void    CalcCriticalChance(cDamagePacket *packet);
  virtual void    CalcDodgeChance(cDamagePacket *packet);
  virtual void    CalcOffense(cDamagePacket *packet);
  virtual void    CalcDefense(cDamagePacket *packet);
  virtual void    CalcSpellPower(cCalcPacket *packet);

  virtual void    CalcCarryingCapacity(cCalcPacket *packet); 
  virtual void    CalcExperienceGain(cCalcPacket *packet); 

  virtual void    CalcAttackSpeed(cCalcPacket *packet); 
  virtual void    CalcMovementSpeed(cCalcPacket *packet); 

protected:

};

//---------------------------------------------- Class Declarations
class cGlobalPropRule : public cRule
{
public:
  virtual const char * GetName(){return "Global Prop";};

  virtual void    CalcHealthMax(cCalcPacket *packet);

  virtual void    CalcSpellPower(cCalcPacket *packet);
};

class cGlobalMarkerRule : public cRule
{
public:
  virtual const char * GetName(){return "Global Marker";};

  virtual void    CalcSpellPower(cCalcPacket *packet);
};

//----------------------------------------- static variable definition
//----------------------------------------- Functions Declarations

void 
AddGlobalCharacterRules(cRuleContainer *container)
{
  container->Add(SyNew cGlobalCombatRule);
}

void 
AddGlobalPropRules(cRuleContainer *container)
{
  container->Add(SyNew cGlobalPropRule);
}

void 
AddGlobalMarkerRules(cRuleContainer *container)
{
  container->Add(SyNew cGlobalMarkerRule);
}
//------------------------------------ Member Functions Definitions


void    
cGlobalCombatRule::CalcHealthMax(cCalcPacket *packet)
{
  cGameObject *obj = mpTitan->GetRegistry()->Fetch(packet->mObjectID);
  SyAssertf(obj!=NULL,"Bad packet");
  cStatsCharacter *stats = (cStatsCharacter *)obj->GetStats();

  const cCharacterLevel *pLevel = stats->GetLevel();
  const cCharacterClass *pClass = stats->GetClass();

  if (obj->GetType() == cGameObject::OBJ_PLAYER)
  {
    packet->SetTotal((int)pLevel->mMaxPlayerHP,"Level",GetName());
    packet->MultiplyTotal(pClass->mMaxHP,"Class",GetName());
    packet->AddTotal(stats->GetBonusHealth(),"Bonus Health",GetName());
  }
  else
  {
    SyAssert(obj->GetType() == cGameObject::OBJ_NPC);

    packet->SetTotal((int)pLevel->mMaxNPCHP,"Level",GetName());
    packet->MultiplyTotal(pClass->mMaxHP,"Class",GetName());

    const cDifficultySettingsTable* pDSTable = mpTitan->GetDatabaseSys()->GetDifficultySettingsTable();
    SyAssert(mpTitan->GetDifficultySetting()>=1 && mpTitan->GetDifficultySetting()<= cDifficultySettingsTable::MAX_DIFFICULTY_LEVELS);
    packet->MultiplyTotal(pDSTable->mHealthMultipliers[mpTitan->GetDifficultySetting()-1],"Difficulty Health Multiplier",GetName());
  }
}
 
void    
cGlobalCombatRule::CalcHealthRegen(cCalcPacket *packet)
{
  packet->SetTotal(0,"Base",GetName());
};

void    
cGlobalCombatRule::CalcBlockMax(cCalcPacket *packet)
{
  cGameObject *obj = mpTitan->GetRegistry()->Fetch(packet->mObjectID);
  SyAssertf(obj!=NULL,"Bad packet");
  cStatsCharacter *stats = (cStatsCharacter *)obj->GetStats();

  const cCharacterLevel *pLevel = stats->GetLevel();
  packet->SetTotal((int)pLevel->mStats.mBlockMeter,"Level",GetName());

  const cCharacterClass *pClass = stats->GetClass();
  packet->MultiplyTotal(pClass->mStats.mBlockMeter,"Class",GetName());

  if (obj->GetType() == cGameObject::OBJ_NPC)
  {
    const cDifficultySettingsTable* pDSTable = mpTitan->GetDatabaseSys()->GetDifficultySettingsTable();
    SyAssert(mpTitan->GetDifficultySetting()>=1 && mpTitan->GetDifficultySetting()<= cDifficultySettingsTable::MAX_DIFFICULTY_LEVELS);
    packet->MultiplyTotal(pDSTable->mBlockMultipliers[mpTitan->GetDifficultySetting()-1],"Difficulty Block Multiplier",GetName());
  }
}

void    
cGlobalCombatRule::CalcBlockRegen(cCalcPacket *packet)
{
  packet->SetTotal(SY_ROUND(l_BLOCK_REGEN*100.0f), "Level", GetName());

//  cGameObject *obj = mpTitan->GetRegistry()->Fetch(packet->mObjectID);
//  SyAssertf(obj!=NULL,"Bad packet");
//  cStatsCharacter *stats = (cStatsCharacter *)obj->GetStats();

//  const cCharacterLevel *pLevel = stats->GetLevel();
//  int total = ((int)pLevel->mStats.mBlockMeter) * 100;  
//  packet->SetTotal(total,"Level",GetName());
//
//  const cCharacterClass *pClass = stats->GetClass();
//  packet->MultiplyTotal(pClass->mStats.mBlockMeter,"Class",GetName());
};


void    
cGlobalCombatRule::CalcManaMax(cCalcPacket *packet)
{
  cGameObject *obj = mpTitan->GetRegistry()->Fetch(packet->mObjectID);
  SyAssertf(obj!=NULL,"Bad packet");
  cStatsCharacter *stats = (cStatsCharacter *)obj->GetStats();

  const cCharacterLevel *pLevel = stats->GetLevel();
  const cCharacterClass *pClass = stats->GetClass();

  if (obj->GetType() == cGameObject::OBJ_PLAYER)
  {
    packet->SetTotal((int)pLevel->mMaxPlayerMana,"Level",GetName());
    packet->MultiplyTotal(pClass->mMaxMana,"Class",GetName());
    packet->AddTotal(stats->GetBonusMana(),"Bonus",GetName());
  }
  else
  {
    SyAssert(obj->GetType() == cGameObject::OBJ_NPC);
    packet->SetTotal((int)pLevel->mMaxNPCMana,"Level",GetName());
    packet->MultiplyTotal(pClass->mMaxMana,"Class",GetName());
  }
}

void    
cGlobalCombatRule::CalcManaRegen(cCalcPacket *packet)
{
  packet->SetTotal(0,"Base",GetName());
};

void    
cGlobalCombatRule::CalcDamageInitial(cDamagePacket *packet)
{
  cGameObject *obj = mpTitan->GetRegistry()->Fetch(packet->mObjectID);
  SyAssertf(obj!=NULL,"Bad packet");
  cStatsCharacter *stats = (cStatsCharacter *)obj->GetStats();

  const char *name="";
  int damage=0;
  cItem *weapon=NULL; 

  cInventoryCharacter *inv = prop_cast<cInventoryCharacter *>(obj->GetInventory());
  if (inv==NULL) {SyAssertf(0,"Unknown Inventory?");return;}

  if (packet->mbRanged)
  {
    weapon = inv->GetEquippedItem(EQUIP_RANGED);
  }
  else
  {
    weapon = inv->GetEquippedItem(EQUIP_MELEE);
  }

  const cStatsItemMaster *master=NULL;

  if (weapon && weapon->GetMaster() && weapon->GetMaster()->mBaseDamageBonus > 0)
  {
    master = weapon->GetMaster();
    damage = master->mBaseDamageBonus;
    name = master->mID.GetName();
  }
  else
  {
    tGameID natural ;
    if (packet->mbRanged)
    {
      natural = stats->GetMaster()->mNaturalRanged;
    }
    else
    {
      natural = stats->GetMaster()->mNaturalMelee;
    }

    if (natural != ID_NONE)
    {
      master = mpTitan->GetDatabaseSys()->GetItemMaster(natural);
      SyAssertf(master!=NULL,"Unknown natural weapon master for %s(%s)",obj->GetName(),stats->GetMaster()->mID.GetName());
      damage = master->mBaseDamageBonus;
      name = master->mID.GetName();
    }
  }

  if (damage <= 0) // use stat based damage
  {
    const cCharacterClass *pClass = stats->GetClass();
    const cCharacterLevel *pLevel = stats->GetLevel();

    if (obj->GetType() == cGameObject::OBJ_PLAYER)
    {
      damage = (int)(pLevel->mPlayerDamage * pClass->mDamage); 
    }
    else
    {
      SyAssert(obj->GetType() == cGameObject::OBJ_NPC);
      damage = (int)(pLevel->mNPCDamage * pClass->mDamage); 
    }

    name = "Class/Level table"; 
  }

  packet->SetTotal(damage,name,GetName());

  if (obj->GetType() == cGameObject::OBJ_NPC)
  {
    const cDifficultySettingsTable* pDSTable = mpTitan->GetDatabaseSys()->GetDifficultySettingsTable();
    packet->MultiplyTotal(pDSTable->mDamageMultipliers[mpTitan->GetDifficultySetting()-1],"Difficulty Damage Multiplier",GetName());
  }
}

void    
cGlobalCombatRule::CalcDamageRaw(cDamagePacket *packet)
{
  cGameObject *obj = mpTitan->GetRegistry()->Fetch(packet->mObjectID);
  SyAssertf(obj!=NULL,"Bad packet");

  if (NUM_COMBOS != packet->mCombo)
  {
    cAnimCharControllerInterface *interface = ((cGraphicCharacter *) obj->GetGraphic())->GetAnimController();
    packet->MultiplyTotal(interface->GetDamageMultiplier(packet->mCombo),"Combo Multiplier",GetName());
  }

  // calc critical hit damage
  cDamagePacket critPacket;
  critPacket.mPacketType = cRulePacket::CALC_CRITICAL_CHANCE;
  critPacket.SetTotal(0, "Initial Critical Chance", GetName());
  critPacket.mAttackerID = packet->mAttackerID;
  critPacket.mDefenderID = packet->mDefenderID;
  critPacket.mCombo = packet->mCombo;
  critPacket.mSpellID = packet->mSpellID;
  critPacket.mbRanged = packet->mbRanged;

  obj->GetStats()->ProcessRulePacket(&critPacket);

  if (critPacket.GetTotal() > 0 && mpTitan->Random(1, 100) <= critPacket.GetTotal())
  {
    packet->MultiplyTotal(2.0f, "Add Critical Hit Damage", GetName());
  }
}

void    
cGlobalCombatRule::CalcDamageReduced(cDamagePacket *packet)
{
  cGameObject *defender = mpTitan->GetRegistry()->Fetch(packet->mDefenderID);
  SyAssertf(defender!=NULL,"Bad packet");
  cStatsCharacter *defender_stats = (cStatsCharacter *)defender->GetStats();

  if (ID_NONE != packet->mSpellID && defender)
  {
    cStatsCharacter* pDefenderStats = prop_cast<cStatsCharacter*>(defender->GetStats());

    const cSpellMaster* pSpell = mpTitan->GetDatabaseSys()->GetSpellMaster(packet->mSpellID);
    SyAssert(pSpell!=NULL);

    if (pSpell && pDefenderStats)
    {
      packet->MultiplyTotal(pDefenderStats->GetClass()->mElementalMultipliers[pSpell->mElementalType], "Elemental Multiplier", GetName());
    }
  }

  // calculate defense
  cDamagePacket defense_packet = *packet;
  defense_packet.mPacketType = cRulePacket::CALC_DEFENSE;
  defense_packet.Reset();
  defender_stats->ProcessRulePacket(&defense_packet);

  int defense = defense_packet.GetTotal();

  int offense = defense;
  cGameObject *attacker = mpTitan->GetRegistry()->Fetch(packet->mAttackerID);
  if (attacker != NULL)
  {
    cStatsCharacter *attacker_stats = NULL;
    attacker_stats = (cStatsCharacter *)attacker->GetStats();
    // calculate offense

    cDamagePacket offense_packet = *packet;
    offense_packet.mPacketType = cRulePacket::CALC_OFFENSE;
    offense_packet.Reset();
    attacker_stats->ProcessRulePacket(&offense_packet);

    offense = offense_packet.GetTotal();
  }

  if (ID_NONE != packet->mSpellID)
  {
    // spell power already factored in, just divide by defense
    packet->MultiplyTotal(1.0f / (float)defense, "Defense Mod", GetName());
  }
  else
  {
    float mod; 
    if (offense > defense)
    {
      float effective_offense = (float)offense;
      if (offense >1.5f * defense) effective_offense = 1.5f * defense;
      mod = ((float)defense) / ((2.0f*defense)-effective_offense);
    }
    else
    {
      mod = (float)offense / (float)defense;
    }
    if (mod > 2.0f) mod = 2.0f;
    if (mod < 0.5f) mod = 0.5f;

    packet->MultiplyTotal(mod, "Defense Mod", GetName());
  }
}

void    
cGlobalCombatRule::CalcDefense(cDamagePacket *packet)
{
  cGameObject *obj = mpTitan->GetRegistry()->Fetch(packet->mObjectID);
  SyAssertf(obj!=NULL,"Bad packet");
  cStatsCharacter *stats = (cStatsCharacter *)obj->GetStats();

  const cCharacterClass *pClass = stats->GetClass();
  const cCharacterLevel *pLevel = stats->GetLevel();

  int total = 0;
  int bonus =0;
  float mult=1.0f;

  switch (packet->GetDamageType())
  {
    case DT_NONE:
    case DT_PHYSICAL:
        total =(int)pLevel->mStats.mPhysicalDefense;
        bonus = stats->GetBonusMeleeDefense();
        mult  =     pClass->mStats.mPhysicalDefense;
    break;
    case DT_MAGICAL:
        total =(int)pLevel->mStats.mMagicalDefense;
        bonus = stats->GetBonusSpellDefense();
        mult  =     pClass->mStats.mMagicalDefense;
    break;
    default: 
      SyAssert(false);
      break;
  }

  packet->SetTotal((int)total,"Level",GetName());
  packet->MultiplyTotal(mult,"Class",GetName());
  packet->AddTotal(bonus,"Bonus",GetName());
};

void    
cGlobalCombatRule::CalcOffense(cDamagePacket *packet)
{
  cGameObject *obj = mpTitan->GetRegistry()->Fetch(packet->mObjectID);
  SyAssertf(obj!=NULL,"Bad packet");
  cStatsCharacter *stats = (cStatsCharacter *)obj->GetStats();

  const cCharacterClass *pClass = stats->GetClass();
  const cCharacterLevel *pLevel = stats->GetLevel();

  int total = 0;
  int bonus = 0;
  float mult=1.0f;

  if (packet->mbMagic)
  {
    total =(int)pLevel->mStats.mSpellPower;
    bonus = stats->GetBonusMagicPower();
    mult  =     pClass->mStats.mSpellPower;
  }
  else
  {
    total =(int)pLevel->mStats.mAttackPower;
    bonus = stats->GetBonusAttackPower();
    mult  =     pClass->mStats.mAttackPower;
  }

  packet->SetTotal(total,"Level",GetName());
  packet->MultiplyTotal(mult,"Class",GetName());
  packet->AddTotal(bonus,"Bonus",GetName());
}

void
cGlobalCombatRule::CalcSpellPower(cCalcPacket *packet)
{
  cGameObject *obj = mpTitan->GetRegistry()->Fetch(packet->mObjectID);
  SyAssertf(obj!=NULL,"Bad packet");
  SyAssertf(prop_cast<cStatsCharacter*>(obj->GetStats())!=NULL,"Bad packet");

  cStatsCharacter *stats = (cStatsCharacter *)obj->GetStats();

  const cCharacterClass *pClass = stats->GetClass();
  const cCharacterLevel *pLevel = stats->GetLevel();

  packet->SetTotal((int)pLevel->mStats.mSpellPower,"Level",GetName());
  packet->MultiplyTotal(pClass->mStats.mSpellPower,"Class",GetName());
  packet->AddTotal(stats->GetBonusMagicPower(),"Bonus",GetName());
}

void    
cGlobalCombatRule::CalcStrikethrough(cDamagePacket *packet)
{
  cGameObject *obj = mpTitan->GetRegistry()->Fetch(packet->mObjectID);
  SyAssertf(obj!=NULL,"Bad packet");
  cStatsCharacter *stats = (cStatsCharacter *)obj->GetStats();

  const cCharacterLevel *pLevel = stats->GetLevel();
  packet->SetTotal((int)pLevel->mStats.mStrikethrough,"Level",GetName());

  const cCharacterClass *pClass = stats->GetClass();
  packet->MultiplyTotal(pClass->mStats.mStrikethrough,"Class",GetName());

  cGraphicCharacter *pGraphic = prop_cast<cGraphicCharacter *>(obj->GetGraphic());
  if (pGraphic)
  {
    packet->MultiplyTotal(pGraphic->GetAnimController()->GetStrikethroughMultiplier(packet->mCombo),"Animation",GetName());
  }
}


void    
cGlobalCombatRule::CalcCriticalChance(cDamagePacket *packet)
{
  packet->SetTotal(0, "Default Critical Chance", "Global Combat Rule");
}

void    
cGlobalCombatRule::CalcDodgeChance(cDamagePacket *packet)
{
  packet->SetTotal(0, "Default Dodge Chance", "Global Combat Rule");

  cGameObject *obj = mpTitan->GetRegistry()->Fetch(packet->mDefenderID);
  SyAssertf(obj!=NULL,"Bad packet");

  if (obj)
  {
    cGraphicCharacter* pGraphic = prop_cast<cGraphicCharacter*>(obj->GetGraphic());

    if (pGraphic)
    {
      if (pGraphic->GetAnimController()->IsDodging())
      {
        packet->SetTotal(100, "Default Dodge Chance", "Global Combat Rule");
      }
    }
  }
}

void  
cGlobalCombatRule::CalcCarryingCapacity(cCalcPacket *packet)
{
} 

void    
cGlobalCombatRule::CalcExperienceGain(cCalcPacket *packet)
{
} 

void
cGlobalCombatRule::CalcAttackSpeed(cCalcPacket *packet)
{
  packet->SetTotal(100, "Normal Attack Speed", "Global Combat Rule");
}

void
cGlobalCombatRule::CalcMovementSpeed(cCalcPacket *packet)
{
  packet->SetTotal(100, "Normal Movement Speed", "Global Combat Rule");
}


//------------------------------------------------------------------------
void    
cGlobalPropRule::CalcHealthMax(cCalcPacket *packet)
{
  cGameObject *obj = mpTitan->GetRegistry()->Fetch(packet->mObjectID);
  SyAssertf(obj!=NULL,"Bad packet");
  SyAssertf(prop_cast<cStatsProp*>(obj->GetStats())!=NULL, "invalid object with global prop rule");
  cStatsProp *stats = static_cast<cStatsProp*>(obj->GetStats());

  packet->SetTotal(stats->GetMaster()->mMaxHealth,"Max Health",GetName());
}

void
cGlobalPropRule::CalcSpellPower(cCalcPacket *packet)
{
  // no this is not crazy, props cast spells from script (such as traps)
  // they need to have a spell power equal to the best player's spell power
  // to tune trap damage as they play
  cGameObject* pPlayer = mpTitan->GetRegistry()->BeginType(cGameObject::OBJ_PLAYER);

  cCalcPacket playerSpellPower;
  int maxSpellPower = 0;

  while (pPlayer)
  {
    playerSpellPower.mPacketType = cRulePacket::CALC_SPELL_POWER;
    playerSpellPower.SetTotal(0, "Player Spell Power", GetName());
    playerSpellPower.mObjectID = pPlayer->GetID();
    pPlayer->GetStats()->ProcessRulePacket(&playerSpellPower);

    if (playerSpellPower.GetTotal() > maxSpellPower)
    {
      maxSpellPower = playerSpellPower.GetTotal();
    }

    pPlayer = mpTitan->GetRegistry()->NextType(pPlayer);
  }

  packet->SetTotal(maxSpellPower,"Prop Spell Power",GetName());
}

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

void
cGlobalMarkerRule::CalcSpellPower(cCalcPacket *packet)
{
  // no this is not crazy, markers cast spells from script (such as traps)
  // they need to have a spell power equal to the best player's spell power
  // to tune trap damage as they play
  cGameObject* pPlayer = mpTitan->GetRegistry()->BeginType(cGameObject::OBJ_PLAYER);

  cCalcPacket playerSpellPower;
  int maxSpellPower = 0;

  while (pPlayer)
  {
    playerSpellPower.mPacketType = cRulePacket::CALC_SPELL_POWER;
    playerSpellPower.SetTotal(0, "Player Spell Power", GetName());
    playerSpellPower.mObjectID = pPlayer->GetID();
    pPlayer->GetStats()->ProcessRulePacket(&playerSpellPower);

    if (playerSpellPower.GetTotal() > maxSpellPower)
    {
      maxSpellPower = playerSpellPower.GetTotal();
    }

    pPlayer = mpTitan->GetRegistry()->NextType(pPlayer);
  }

  packet->SetTotal(maxSpellPower,"Marker Spell Power",GetName());
}

//------------------------------------------------------------------------
void 
GlobalRules_RegisterTuningVariables()
{
  gTuningSys.AddFloat(&l_BLOCK_REGEN,"Block_Regen");
}

// EOF
