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

//-------------------------------------------------------- Includes
#include "areaeffect.h"
#include "registry.h"
#include "stats.h"
#include "titan.h"
#include "intel.h"
#include "script_pawn.h"
#include "physics.h"
//---------------------------------------------- Class Declarations
//----------------------------------------- Functions Declarations
//------------------------------------ Member Functions Definitions


//---------------------------------------------------- cGameEffect_ScriptCall

cGameEffect_ScriptCall::cGameEffect_ScriptCall(Titan *titan) :
cGameEffect(titan),
mOwner(ID_NONE)
{
}

void  
cGameEffect_ScriptCall::OnEnter(tGameObjectID id)
{
  mpTitan->GetScriptSys()->ScriptEvent(PET_TRIGGER_ENTER,mOwner,id);
}

void  
cGameEffect_ScriptCall::OnExit(tGameObjectID id)
{
  mpTitan->GetScriptSys()->ScriptEvent(PET_TRIGGER_EXIT,mOwner,id);
}


//---------------------------------------------------- cGameEffect_Damage 


cGameEffect_Damage::cGameEffect_Damage(Titan *titan) :
  cGameEffect(titan),
  mSource(ID_NONE),
  mDamage(0),
  mNormalType(NDT_NONE),
  mMagicType(MDT_NONE)
{
};

void  
cGameEffect_Damage::SetDamage(int damage,eNormalDamageType normalType,eMagicDamageType magicType)
{
  mDamage = damage;
  mNormalType = normalType;
  mMagicType = magicType;
};

  
void  
cGameEffect_Damage::OnEnter(tGameObjectID target_id)
{

  cGameObject *target_obj = mpTitan->GetRegistry()->Fetch(target_id);

  if (target_obj == NULL)
  {
    return;
  }
  // check network ownership requirements
  if (target_obj->IsRemote())
  {
    return;
  }

    // blocking
  cDamagePacket damage;

  damage.AddTotal(mDamage,"Effect","Area");
  damage.mbRanged = true;
  damage.mAttackerID = mSource;
  damage.mDefenderID = target_id;

  target_obj->GetStats()->ApplyDamage(&damage);
}

//---------------------------------------------------- cGameEffect_Attack 


cGameEffect_Attack::cGameEffect_Attack(Titan *titan) :
  cGameEffect(titan),
  mSource(ID_NONE),
  mAttackIndex(0)
{
};

void  
cGameEffect_Attack::OnEnter(tGameObjectID target_id)
{
  cGameObject *obj = mpTitan->GetRegistry()->Fetch(target_id);
  if (obj == NULL)
  {
    return;
  }

  ((cStatsCharacter *)obj->GetStats())->AttackFrame(mSource,(eComboType)mAttackIndex);
}

//------------------------------------------------------ cAreaEffect

cGameEffect_Knockback::cGameEffect_Knockback(Titan *titan) :
  cGameEffect(titan),
    mXZAmount(8.0f),
    mYAmount(3.5f)
{
}

void  
cGameEffect_Knockback::OnEnter(tGameObjectID target_id)
{

  cGameObject *target_obj = mpTitan->GetRegistry()->Fetch(target_id);

  if (target_obj == NULL)
  {
    return;
  }
  // check network ownership requirements
  if (target_obj->IsRemote())
  {
    return;
  }

  target_obj->GetPhysics()->Knockback(mLocation,mXZAmount,mYAmount);
}



//------------------------------------------------------ cAreaEffect


cAreaEffect::cAreaEffect(Titan *titan): 
  mpTitan(titan),
  mSource(ID_NONE)
{
}

cAreaEffect::~cAreaEffect()
{
  for (int ii = mEffects.Begin();ii!=mEffects.End();ii=mEffects.Next(ii))
  {
    delete mEffects(ii);
  }
  mEffects.Clear();
}

void 
cAreaEffect::Update(float time)
{
  UpdateIncluded(time);
}

void 
cAreaEffect::UpdateIncluded(float time)
{
  cGameObjectRegistry *registry = mpTitan->GetRegistry();
  for (int ii=registry->Begin();ii!=registry->End();ii= registry->Next(ii))
  {
    cGameObject *obj = (*registry)(ii);
    if (obj->GetID()== mSource)
    {
      continue;
    }
    if (!IsInside(obj))
    {
      continue;
    }

    if (!IsIncluded(obj))
    {
      OnEnter(obj);
      mIncluded.Add(obj->GetID());
    }

    UpdateEffect(obj,time);
  }

  for (int ii=mIncluded.Begin();ii!=mIncluded.End();)
  {
    cGameObject *obj = registry->Fetch(mIncluded(ii));
    if (obj == NULL || !IsInside(obj))
    {
      if (obj != NULL)
      {
        OnExit(obj);
      }
      ii=mIncluded.ReplaceLast(ii); 
    }
    else
    {
      ii= mIncluded.Next(ii);
    }
  }
};

void  
cAreaEffect::OnEnter(cGameObject* obj)
{
  for (int ii = mEffects.Begin();ii!=mEffects.End();ii=mEffects.Next(ii))
  {
    mEffects(ii)->OnEnter(obj->GetID());
  }
};

void  
cAreaEffect::OnExit(cGameObject* obj)
{
  for (int ii = mEffects.Begin();ii!=mEffects.End();ii=mEffects.Next(ii))
  {
    mEffects(ii)->OnExit(obj->GetID());
  }
}; 

void  
cAreaEffect::UpdateEffect(cGameObject* obj,float time)
{
  for (int ii = mEffects.Begin();ii!=mEffects.End();ii=mEffects.Next(ii))
  {
    mEffects(ii)->Update(obj->GetID(),time);
  }
}; 

bool  
cAreaEffect::IsIncluded(cGameObject *obj)
{
  for (int ii=mIncluded.Begin();ii!=mIncluded.End();ii= mIncluded.Next(ii))
  {
    if (mIncluded(ii)==obj->GetID())
    {
      return true;
    }
  }
  return false;
};

//------------------------------------------------------------ cAreaEffect_Radius  

cAreaEffect_Radius::cAreaEffect_Radius(Titan *titan) :
 cAreaEffect(titan),
 mRadius(0)
{
};


bool  
cAreaEffect_Radius::IsInside(cGameObject *obj)
{         

  cGameObject::tObjectType type = obj->GetType();

  if (type != cGameObject::OBJ_PLAYER && type != cGameObject::OBJ_NPC && type != cGameObject::OBJ_PROP)
  {
    return false;
  }

  float dist = obj->GetDistance(mLocation);

  return (dist < mRadius);
};

//------------------------------------------------------------ cAreaEffect_Burst  

cAreaEffect_Burst::cAreaEffect_Burst(Titan *titan)
 : cAreaEffect_Radius(titan),
   mSpeed(0.0f),
   mMaxRadius(1.0f)
{
};

void  
cAreaEffect_Burst::Update(float time)
{
  mRadius += time * mSpeed;

  if (mRadius > mMaxRadius)
  {
    mRadius = mMaxRadius;
  }

  cAreaEffect_Radius::Update(time);
}

bool  
cAreaEffect_Burst::IsExpired()
{
  return (mRadius == mMaxRadius);
}

//------------------------------------------------------------ cAreaEffect_ChainLightning  
cAreaEffect_ChainLightning::cAreaEffect_ChainLightning(Titan *titan)
: cAreaEffect(titan),
  mDuration(1.0f),
  mTargetDelay(1.0f),
  mTargetDelayTimer(1.0f),
  mMaxDist(5.0f),
  mMaxTargets(1),
  mNextTargetID(ID_NONE)
{
}

void
cAreaEffect_ChainLightning::Update(float time)
{
  cAreaEffect::Update(time);

  mDuration -= time;

  mTargetDelayTimer += time;

  if ((mIncluded.Size() < mMaxTargets ) && (ID_NONE == mNextTargetID))
  {
    mNextTargetID = SelectNextTarget();
  }
}

bool
cAreaEffect_ChainLightning::IsExpired()
{
  return mDuration <= 0.0f;
}

bool
cAreaEffect_ChainLightning::IsInside(cGameObject *obj)
{
  bool bInside = IsIncluded(obj);

  if (!bInside && mTargetDelayTimer >= mTargetDelay)
  {
    if (obj->GetID() == mNextTargetID)
    {
      mTargetDelayTimer = 0.0f;
      mNextTargetID = ID_NONE;
      return true;
    }
  }

  return bInside;
}

tGameObjectID cAreaEffect_ChainLightning::SelectNextTarget()
{
  SyAssertf(ID_NONE != mSource, "Chain lightning effect needs a source");

  if (ID_NONE == mSource)
  {
    return ID_NONE;
  }

  cGameObjectRegistry* pRegistry = mpTitan->GetRegistry();
  cGameObject* pSourceObj = NULL;
  pSourceObj = mpTitan->GetRegistry()->Fetch(mSource);

  if (!pSourceObj)
  {
    return ID_NONE;
  }

  cIntel* pIntel = pSourceObj->GetIntel();

  // use last target for position tests if we have one
  if (mIncluded.Size() > 0)
  {
    pSourceObj = mpTitan->GetRegistry()->Fetch(mIncluded(mIncluded.Prev(mIncluded.End()))); 
    if (!pSourceObj)
    {
      return ID_NONE;   // must've been deleted
    }    
  }

  cGameObject* pCurObj;
  float dist, bestDist = 0.0f;
  int bestIndex = -1;

  for (int i=pRegistry->Begin(); i!=pRegistry->End(); i=pRegistry->Next(i))
  {
    pCurObj = (*pRegistry)(i);

    if (!pCurObj || !pIntel->IsTargetable(pCurObj) || IsIncluded(pCurObj))
    {
      continue;
    }
    
    dist = pCurObj->GetDistance(pSourceObj);

    if (dist < mMaxDist &&
        (-1 == bestIndex || dist < bestDist))
    {
      bestDist = dist;
      bestIndex = i;
    }
  }

  if (bestIndex >= 0)
  {
    return (*pRegistry)(bestIndex)->GetID();
  }

  return ID_NONE;
}


//------------------------------------------------------------ cAreaEffect_Arc  

cAreaEffect_Arc::cAreaEffect_Arc(Titan *titan) :
  cAreaEffect(titan),
  mSpeed(1.0f),
  mRadius(1.0f),
  mStart(0.0f),
  mEnd(0.0f),
  mCur(0.0f)
{
}

void          
cAreaEffect_Arc::SetArc(const SyVect3 &loc,float speed, float radius,float start,float end)
{
  mSpeed = speed;
  mRadius = radius;
  mStart = start;
  mEnd = end;
  mCur = start;
  mLoc = loc;

  if ((mStart < mEnd && mSpeed < 0) ||
      (mStart > mEnd && mSpeed > 0))
  {
    mSpeed = -mSpeed;
  }

  SyAssertf(speed > 0.0001f,"Bad arc speed");
};

void  
cAreaEffect_Arc::Update(float time)
{
  mCur += time * mSpeed;
}

bool  
cAreaEffect_Arc::IsExpired()
{
  if (mSpeed > 0)
  {
    return (mCur > mEnd);
  }
  else
  {
    return (mCur < mEnd);
  }
}

bool  
cAreaEffect_Arc::IsInside(cGameObject *obj)
{
  float dist = obj->GetDistance(mLoc);

  if (dist > mRadius)
  {
    return false;
  }

  float heading = obj->GetHeadingTowards(mLoc);

  // need to normalize?
  if (mSpeed > 0)
  {
    if (heading < mCur && heading > mStart)
    {
      return true;
    }
  }
  else
  {
    if (heading > mCur && heading < mStart)
    {
      return true;
    }
  }
  return false;

}

//------------------------------------------------------------ cAreaEffectSys  

cAreaEffectSys::~cAreaEffectSys()
{
  Clear();
};

void
cAreaEffectSys::Clear()
{
  for (int ii=mAreaEffects.Begin();ii!=mAreaEffects.End();ii= mAreaEffects.Next(ii))
  {
    delete mAreaEffects(ii);
  }
  mAreaEffects.Clear();
}

void 
cAreaEffectSys::Add(cAreaEffect *effect)
{
  mAreaEffects.Add(effect);
}

void 
cAreaEffectSys::Update(float time)
{
  for (int ii=mAreaEffects.Begin();ii!=mAreaEffects.End();)
  {
    mAreaEffects(ii)->Update(time);

    if (mAreaEffects(ii)->IsExpired())
    {
     ii =  mAreaEffects.ReplaceLast(ii);
    }
    else
    {
      ii= mAreaEffects.Next(ii);
    }
  }
}

// EOF
