/* Copyright (c) 2002-2012 Croteam Ltd. 
This program is free software; you can redistribute it and/or modify
it under the terms of version 2 of the GNU General Public License as published by
the Free Software Foundation


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.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */

345
%{
#include "EntitiesMP/StdH/StdH.h"
#include "ModelsMP/Enemies/CannonStatic/Turret.h"

%}

uses "EntitiesMP/ModelHolder2";
uses "EntitiesMP/Projectile";
uses "EntitiesMP/SoundHolder";
uses "EntitiesMP/BloodSpray";
uses "EntitiesMP/CannonBall";

%{
#define CANNONS_SIZE 2.0f
  
// info structure
static EntityInfo eiCannonStatic = {
  EIBT_WOOD, 10000.0f,
  0.0f, 1.5f*CANNONS_SIZE, 0.0f,     // source (eyes)
  0.0f, 0.5f*CANNONS_SIZE, 0.0f,     // target (body)
};

#define FIRING_POSITION_MUZZLE FLOAT3D(0.0f, 0.4f, -1.0f)
#define MUZZLE_ROTATION_SPEED 45.0f //deg/sec

%}

class CCannonStatic: CEnemyBase {
name      "CannonStatic";
thumbnail "Thumbnails\\CannonStatic.tbn";

properties:
  
  1 FLOAT m_fHealth            "Cannon Health" = 100.0f,
  2 RANGE m_fFiringRangeClose  "Cannon Firing Close Range" = 50.0f,
  3 RANGE m_fFiringRangeFar    "Cannon Firing Far Range" = 150.0f,
  4 FLOAT m_fShootingPeriod    "Cannon Shooting Period" = 5.0f,
  5 FLOAT m_fSize              = CANNONS_SIZE,
  6 FLOAT m_fMaxPitch          "Cannon Max Pitch" = 20.0f,
  7 FLOAT m_fViewAngle         "Cannon View Angle" = 2.5f,
  8 BOOL  m_bActive            "Cannon Active" = TRUE,

 20 FLOAT3D m_fRotSpeedMuzzle  = ANGLE3D(0.0f, 0.0f, 0.0f),
 25 FLOAT m_fDistanceToPlayer   = 0.0f,
 26 FLOAT m_fDesiredMuzzlePitch = 0.0f,
 27 INDEX m_iMuzzleDir = 1,
 28 FLOAT3D m_vFiringPos      = FLOAT3D(0.0f, 0.0f, 0.0f),
 29 FLOAT3D m_vTarget = FLOAT3D(0.0f, 0.0f, 0.0f),

 40 FLOAT3D m_aBeginMuzzleRotation = ANGLE3D(0.0f, 0.0f, 0.0f),
 41 FLOAT3D m_aEndMuzzleRotation = ANGLE3D(0.0f, 0.0f, 0.0f),

components:
  1 class CLASS_BASE          "Classes\\EnemyBase.ecl",
  2 class CLASS_BASIC_EFFECT  "Classes\\BasicEffect.ecl",
  3 class CLASS_PROJECTILE    "Classes\\Projectile.ecl",
  4 class CLASS_CANNONBALL    "Classes\\CannonBall.ecl",
  //5 class CLASS_BLOOD_SPRAY   "Classes\\BloodSpray.ecl",
  
 // ************** CANNON MODEL **************
 10 model MODEL_TURRET        "ModelsMP\\Enemies\\CannonStatic\\Turret.mdl",
 11 model MODEL_CANNON        "ModelsMP\\Enemies\\CannonStatic\\Cannon.mdl",
 20 texture TEXTURE_TURRET    "ModelsMP\\Enemies\\CannonStatic\\Turret.tex",
 21 texture TEXTURE_CANNON    "Models\\Weapons\\Cannon\\Body.tex",

 // ************** CANNON PARTS **************
 30 model MODEL_DEBRIS_MUZZLE "ModelsMP\\Enemies\\CannonStatic\\Debris\\Cannon.mdl",
 31 model MODEL_DEBRIS_WHEEL  "ModelsMP\\Enemies\\CannonStatic\\Debris\\Wheel.mdl",
 32 model MODEL_DEBRIS_WOOD   "ModelsMP\\Enemies\\CannonStatic\\Debris\\Wood.mdl",
 35 model   MODEL_BALL         "Models\\Weapons\\Cannon\\Projectile\\CannonBall.mdl",
 36 texture TEXTURE_BALL       "Models\\Weapons\\Cannon\\Projectile\\IronBall.tex",

 // ************** SOUNDS **************
 50 sound   SOUND_FIRE         "ModelsMP\\Enemies\\CannonStatic\\Sounds\\Fire.wav",

functions:                                        

virtual CTString GetPlayerKillDescription(const CTString &strPlayerName, const EDeath &eDeath)
  {
    CTString str;
    str.PrintF(TRANSV("A Cannon killed %s"), (const char *) strPlayerName);
    return str;
  }

  /* Entity info */
  void *GetEntityInfo(void) {
    return &eiCannonStatic;
  };

  virtual const CTFileName &GetComputerMessageName(void) const {
    static DECLARE_CTFILENAME(fnmCannon, "DataMP\\Messages\\Enemies\\CannonStatic.txt");
    return fnmCannon;
  };

  void Precache(void) {
    CEnemyBase::Precache();
    PrecacheModel(MODEL_DEBRIS_MUZZLE);
    PrecacheModel(MODEL_DEBRIS_WHEEL);
    PrecacheModel(MODEL_DEBRIS_WOOD);
    PrecacheModel(MODEL_BALL);

    PrecacheTexture(TEXTURE_BALL);

    PrecacheSound(SOUND_FIRE);
    
    PrecacheClass(CLASS_CANNONBALL);
  };


  void ReceiveDamage(CEntity *penInflictor, enum DamageType dmtType,
    FLOAT fDamageAmmount, const FLOAT3D &vHitPoint, const FLOAT3D &vDirection) 
  {
    // take less damage from heavy bullets (e.g. sniper)
    if(dmtType==DMT_BULLET && fDamageAmmount>100.0f)
    {
      fDamageAmmount*=0.5f;
    }

    CEnemyBase::ReceiveDamage(penInflictor, dmtType, fDamageAmmount,
                              vHitPoint, vDirection);    
  };

  // damage anim
  INDEX AnimForDamage(FLOAT fDamage) {
    return 0;
  };

  // death
  INDEX AnimForDeath(void) {
    return 0;
  };

  // cast a ray to entity checking only for brushes
  BOOL IsVisible(CEntity *penEntity) 
  {
    ASSERT(penEntity!=NULL);
    // get ray source and target
    FLOAT3D vSource, vTarget;
    GetPositionCastRay(this, penEntity, vSource, vTarget);

    // cast the ray
    CCastRay crRay(this, vSource, vTarget);
    crRay.cr_ttHitModels = CCastRay::TT_NONE;     // check for brushes only
    crRay.cr_bHitTranslucentPortals = FALSE;
    en_pwoWorld->CastRay(crRay);

    // if hit nothing (no brush) the entity can be seen
    return (crRay.cr_penHit==NULL);     
  };

  BOOL IsInTheLineOfFire(CEntity *penEntity, FLOAT fAngle)
  {
    ASSERT(penEntity!=NULL);

    FLOAT fCosAngle;
    FLOAT3D vHeading;
    FLOAT3D vToPlayer;

    FLOAT3D vSide = FLOAT3D(1.0f, 0.0f, 0.0f)*GetRotationMatrix();
    FLOAT3D vFront = FLOAT3D(0.0f, 0.0f, -1.0f)*GetRotationMatrix();
    vToPlayer = penEntity->GetPlacement().pl_PositionVector - GetPlacement().pl_PositionVector;
    vToPlayer.Normalize();
    
    fCosAngle = vToPlayer%vSide;
    
    // if on the firing plane
    if (Abs(fCosAngle)<CosFast(90.0f-fAngle)) {
      // if in front
      if ((vToPlayer%vFront)>0.0f) {
       return TRUE;
      }
    }
    return FALSE;    
  }

  CPlayer *AcquireTarget() {
    // find actual number of players
    INDEX ctMaxPlayers = GetMaxPlayers();
    CEntity *penPlayer;

    for(INDEX i=0; i<ctMaxPlayers; i++) {
      penPlayer=GetPlayerEntity(i);
      if (penPlayer!=NULL && DistanceTo(this, penPlayer)<m_fFiringRangeFar) {
        // if this player is more or less directly in front of the shooter
        if (IsInTheLineOfFire(penPlayer, m_fViewAngle)) {
          // see if something blocks the path to the player
          if (IsVisible(penPlayer)) {
            return (CPlayer *)penPlayer; 
          }
        }        
      }
    }
    return NULL;
  };

  // spawn body parts
  void CannonBlowUp(void)
  {
    FLOAT3D vNormalizedDamage = m_vDamage-m_vDamage*(m_fBlowUpAmount/m_vDamage.Length());
    vNormalizedDamage /= Sqrt(vNormalizedDamage.Length());
    vNormalizedDamage *= 0.75f;
    vNormalizedDamage += FLOAT3D(0.0f, 10.0f+FRnd()*10.0f, 0.0f);

    FLOAT3D vBodySpeed = en_vCurrentTranslationAbsolute-en_vGravityDir*(en_vGravityDir%en_vCurrentTranslationAbsolute);
    
    // spawn debris
    Debris_Begin(EIBT_WOOD, DPT_NONE, BET_NONE, 1.0f, vNormalizedDamage, vBodySpeed, 5.0f, 2.0f);
    
    Debris_Spawn(this, this, MODEL_DEBRIS_MUZZLE, TEXTURE_CANNON, 0, 0, 0, 0, m_fSize,
      FLOAT3D(FRnd()*0.6f+0.2f, FRnd()*0.6f+0.2f, FRnd()*0.6f+0.2f));
    Debris_Spawn(this, this, MODEL_DEBRIS_WHEEL, TEXTURE_TURRET, 0, 0, 0, 0, m_fSize,
      FLOAT3D(FRnd()*0.6f+0.2f, FRnd()*0.6f+0.2f, FRnd()*0.6f+0.2f));
    Debris_Spawn(this, this, MODEL_DEBRIS_WHEEL, TEXTURE_TURRET, 0, 0, 0, 0, m_fSize,
      FLOAT3D(FRnd()*0.6f+0.2f, FRnd()*0.6f+0.2f, FRnd()*0.6f+0.2f));
    Debris_Spawn(this, this, MODEL_DEBRIS_WOOD, TEXTURE_TURRET, 0, 0, 0, 0, m_fSize,
      FLOAT3D(FRnd()*0.6f+0.2f, FRnd()*0.6f+0.2f, FRnd()*0.6f+0.2f));
    Debris_Spawn(this, this, MODEL_DEBRIS_WOOD, TEXTURE_TURRET, 0, 0, 0, 0, m_fSize,
      FLOAT3D(FRnd()*0.6f+0.2f, FRnd()*0.6f+0.2f, FRnd()*0.6f+0.2f));
    Debris_Spawn(this, this, MODEL_BALL, TEXTURE_BALL, 0, 0, 0, 0, m_fSize/2.0f,
      FLOAT3D(FRnd()*0.6f+0.2f, FRnd()*0.6f+0.2f, FRnd()*0.6f+0.2f));

    // spawn explosion
    CPlacement3D plExplosion = GetPlacement();
    CEntityPointer penExplosion = CreateEntity(plExplosion, CLASS_BASIC_EFFECT);
    ESpawnEffect eSpawnEffect;
    eSpawnEffect.colMuliplier = C_WHITE|CT_OPAQUE;
    eSpawnEffect.betType = BET_CANNON;
    FLOAT fSize = m_fBlowUpSize*1.0f;
    eSpawnEffect.vStretch = FLOAT3D(fSize,fSize,fSize);
    penExplosion->Initialize(eSpawnEffect);

    // spawn shockwave
    plExplosion = GetPlacement();
    penExplosion = CreateEntity(plExplosion, CLASS_BASIC_EFFECT);
    eSpawnEffect.colMuliplier = C_WHITE|CT_OPAQUE;
    eSpawnEffect.betType = BET_CANNONSHOCKWAVE;
    fSize = m_fBlowUpSize*1.0f;
    eSpawnEffect.vStretch = FLOAT3D(fSize,fSize,fSize);
    penExplosion->Initialize(eSpawnEffect);

    // hide yourself (must do this after spawning debris)
    SwitchToEditorModel();
    SetPhysicsFlags(EPF_MODEL_IMMATERIAL);
    SetCollisionFlags(ECF_IMMATERIAL);
  }

  void PreMoving() {
    // manually update rotation of the attachments
    UpdateAttachmentRotations();
    CEnemyBase::PreMoving();
  }

  void PostMoving() {
    CEnemyBase::PostMoving();
    // make sure this entity stays in the moving list
    SetFlags(GetFlags()&~ENF_INRENDERING);
  }

  BOOL AdjustShadingParameters(FLOAT3D &vLightDirection, COLOR &colLight, COLOR &colAmbient) {
    CAttachmentModelObject &amo0 = *GetModelObject()->GetAttachmentModel(TURRET_ATTACHMENT_CANNON);
    // rotate to between-tick position
    amo0.amo_plRelative.pl_OrientationAngle =  Lerp(m_aBeginMuzzleRotation, m_aEndMuzzleRotation, _pTimer->GetLerpFactor());
    return CEnemyBase::AdjustShadingParameters(vLightDirection, colLight, colAmbient);
  };

  void UpdateAttachmentRotations( void )
  {
    // muzzle
    m_aBeginMuzzleRotation = m_aEndMuzzleRotation;
    m_aEndMuzzleRotation += m_fRotSpeedMuzzle*_pTimer->TickQuantum;
  }

  void UpdateFiringPos() {
    FLOATmatrix3D m;
    // initial position
    m_vFiringPos = FIRING_POSITION_MUZZLE*m_fSize;
    
    // pitch rotation
    MakeRotationMatrixFast(m, m_aBeginMuzzleRotation);
    m_vFiringPos = m_vFiringPos*m;
    
    // add translation
    CAttachmentModelObject &amo0 = *GetModelObject()->GetAttachmentModel(TURRET_ATTACHMENT_CANNON);
    m_vFiringPos += amo0.amo_plRelative.pl_PositionVector;    
  }

procedures:
  
  MainLoop() {
    wait() {
      on (EBegin) : {
        call WatchPlayers();
        resume;
      }
      on (EDeactivate) : {
        jump Inactive();
      }
      on (EDeath eDeath) : {
        jump Die(eDeath);
      }
    };
    return;
  };

  
  Die(EDeath eDeath) {
    // not alive anymore
    SetFlags(GetFlags()&~ENF_ALIVE);

    // find the one who killed, or other best suitable player
    CEntityPointer penKiller = eDeath.eLastDamage.penInflictor;
    if (penKiller==NULL || !IsOfClass(penKiller, "Player")) {
      penKiller = m_penEnemy;
    }

    if (penKiller==NULL || !IsOfClass(penKiller, "Player")) {
      penKiller = FixupCausedToPlayer(this, penKiller, /*bWarning=*/FALSE);
    }

    // if killed by someone
    if (penKiller!=NULL) {
      // give him score
      EReceiveScore eScore;
      eScore.iPoints = (INDEX) m_iScore;
      penKiller->SendEvent(eScore);
      if( CountAsKill())
      {
        penKiller->SendEvent(EKilledEnemy());
      }
      // send computer message
      EComputerMessage eMsg;
      eMsg.fnmMessage = GetComputerMessageName();
      if (eMsg.fnmMessage!="") {
        penKiller->SendEvent(eMsg);
      }
    }

    // send event to death target
    SendToTarget(m_penDeathTarget, m_eetDeathType, penKiller);
    // send event to spawner if any
    // NOTE: trigger's penCaused has been changed from penKiller to THIS;
    if (m_penSpawnerTarget) {
      SendToTarget(m_penSpawnerTarget, EET_TRIGGER, this);
    }

    // spawn debris
    CannonBlowUp();
    Destroy();
    return;
  };


  RotateMuzzle() {
        
    FLOAT fDeltaP = m_fDesiredMuzzlePitch - m_aBeginMuzzleRotation(2);

    // if close enough to desired rotation, don't rotate
    if (Abs(fDeltaP)<5.0f) { return EReturn(); }

    m_fRotSpeedMuzzle = ANGLE3D(0.0f, MUZZLE_ROTATION_SPEED*Sgn(fDeltaP), 0.0f);
    autowait(Abs(fDeltaP/MUZZLE_ROTATION_SPEED));
    m_fRotSpeedMuzzle = ANGLE3D(0.0f, 0.0f, 0.0f);
    UpdateFiringPos();
    
    return EReturn();
  };

  FireCannon() {

    FLOAT3D vToTarget = m_penEnemy->GetPlacement().pl_PositionVector -
                        GetPlacement().pl_PositionVector + m_vFiringPos;
    vToTarget.Normalize();
    
    // get vector pointing in heading direction of the muzzle
    FLOAT3D vCannonFront = FLOAT3D(0.0f, 0.0f, -1.0f)*GetRotationMatrix();
    ANGLE aToPlayer = ACos(vToTarget%vCannonFront);

    FLOAT fPitch = aToPlayer + 5.0f;
    FLOAT3D vCannonUp = FLOAT3D(0.0, 1.0f, 0.0f)*GetRotationMatrix();
    
    // if too far, do not fire
    if (m_fDistanceToPlayer>m_fFiringRangeFar) {
      return EReturn();
    // if player under cannon, minimize pitch
    } else if (vToTarget%vCannonUp<0.0f) {
      fPitch = 5.0f;
    // if in far range
    } else if (m_fDistanceToPlayer>m_fFiringRangeClose) {
      if (aToPlayer<m_fMaxPitch) {
        fPitch = aToPlayer + m_fMaxPitch*(m_fDistanceToPlayer-m_fFiringRangeClose)/(m_fFiringRangeFar-m_fFiringRangeClose);
      } else if (TRUE) {
        fPitch = aToPlayer + 10.0f + m_fMaxPitch*(m_fDistanceToPlayer-m_fFiringRangeClose)/(m_fFiringRangeFar-m_fFiringRangeClose);
      }
      // just to make sure
      fPitch = Clamp(fPitch, 1.0f, 80.0f);
    }
   
    m_vTarget = m_penEnemy->GetPlacement().pl_PositionVector;

    m_fDesiredMuzzlePitch = fPitch;
    autocall RotateMuzzle() EReturn;

    FLOAT3D vShooting = GetPlacement().pl_PositionVector + m_vFiringPos;
    FLOAT3D vSpeedDest = FLOAT3D(0.0f, 0.0f, 0.0f);
    FLOAT fLaunchSpeed;
    FLOAT fRelativeHdg;
  
    // calculate parameters for predicted angular launch curve
    EntityInfo *peiTarget = (EntityInfo*) (m_penEnemy->GetEntityInfo());
    CalculateAngularLaunchParams( vShooting, peiTarget->vTargetCenter[1]-6.0f/3.0f, m_vTarget, 
      vSpeedDest, m_fDesiredMuzzlePitch , fLaunchSpeed, fRelativeHdg);

    // target enemy body
    FLOAT3D vShootTarget;
    GetEntityInfoPosition(m_penEnemy, peiTarget->vTargetCenter, vShootTarget);
    // launch
    CPlacement3D pl;
    PrepareFreeFlyingProjectile(pl, vShootTarget, m_vFiringPos, ANGLE3D( fRelativeHdg, m_fDesiredMuzzlePitch, 0));
    CEntityPointer penBall = CreateEntity(pl, CLASS_CANNONBALL);
    ELaunchCannonBall eLaunch;
    eLaunch.penLauncher = this;
    eLaunch.fLaunchPower = fLaunchSpeed;
    eLaunch.cbtType = CBT_IRON;
    eLaunch.fSize = 1.0f;
    penBall->Initialize(eLaunch);
   
    return EReturn();
  };


  WatchPlayers()
  {
    // this is a kind of 'sleep' mode - check to see if any player entered
    // the attack radius every once in a while
    while(TRUE) {
      autowait(0.20f);
      CPlayer *pTarget = AcquireTarget();
      if (pTarget) {
        if ((pTarget->GetFlags()&ENF_ALIVE) && !(pTarget->GetFlags()&ENF_DELETED)) {
          m_penEnemy = pTarget;
          m_fDistanceToPlayer = DistanceTo(this, pTarget);
          autocall FireCannon() EReturn;
          autowait(m_fShootingPeriod);
        }
      }
    }
  }

  Inactive()
  {
    m_fRotSpeedMuzzle  = ANGLE3D(0.0f, 0.0f, 0.0f);
    wait() {
      on (EBegin) : { resume; }
      on (EActivate) : {
        jump MainLoop();
      }
      on (EDeath eDeath) : {
        jump Die(eDeath);
      }
      otherwise (): { resume; }
    }
  }

  Main(EVoid) {
    // declare yourself as a model
    InitAsModel();
    SetPhysicsFlags(EPF_MODEL_WALKING|EPF_HASLUNGS);
    SetCollisionFlags(ECF_MODEL);
    SetFlags(GetFlags()|ENF_ALIVE);
    en_fDensity = 2000.0f;
    
    // set your appearance
    SetModel(MODEL_TURRET);
    SetModelMainTexture(TEXTURE_TURRET);
    
    AddAttachment(TURRET_ATTACHMENT_CANNON, MODEL_CANNON, TEXTURE_CANNON);
    // setup moving speed
    m_fWalkSpeed = 0.0f;
    m_aWalkRotateSpeed = 0.0f;
    m_fAttackRunSpeed = 0.0f;
    m_aAttackRotateSpeed = 0.0f;
    m_fCloseRunSpeed = 0.0f;
    m_aCloseRotateSpeed = 0.0f;
    // setup attack distances
    m_fStopDistance = 100.0f;
    //m_fBlowUpAmount = 65.0f;
    m_fBlowUpAmount = 100.0f;
    m_fBodyParts = 4;
    m_fDamageWounded = 0.0f;
    m_iScore = 750;
    m_sptType = SPT_WOOD;
    
     // properties that modify EnemyBase properties
    if (m_fHealth<=0.0f) { m_fHealth=1.0f; }
    m_fCloseFireTime = m_fAttackFireTime = m_fShootingPeriod;
    SetHealth(m_fHealth); m_fMaxHealth = m_fHealth;
    if (m_fFiringRangeFar<m_fFiringRangeClose) { m_fFiringRangeFar=m_fFiringRangeClose+1.0f; }
        
    // set stretch factors for height and width
    GetModelObject()->StretchModel(FLOAT3D(m_fSize, m_fSize, m_fSize));
    
    ModelChangeNotify();
    StandingAnim();
    
    // don't continue behavior in base class! - this enemy is derived
    // from CEnemyBase only because of its properties
    autowait(0.05f);
    SetDesiredTranslation(FLOAT3D(0.0f, 0.0f, 0.0f));
    UpdateFiringPos();

    if (!m_bActive) { jump Inactive(); }
    jump MainLoop();
    
  };
};