/* 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. */

347
%{
#include "StdH.h"
#include "EntitiesMP/BackgroundViewer.h"
#include "EntitiesMP/WorldSettingsController.h"
#include "ModelsMP/Enemies/Summoner/Summoner.h"
#include "ModelsMP/Enemies/Summoner/Staff.h"
#include "EntitiesMP/Effector.h"
%}

uses "EntitiesMP/EnemyBase";
uses "EntitiesMP/SpawnerProjectile";
uses "EntitiesMP/AreaMarker";
uses "EntitiesMP/SummonerMarker";
uses "EntitiesMP/Player";

event ESummonerTeleport {
  FLOAT fWait,
};

%{
#define RAND_05 (FLOAT(rand())/RAND_MAX-0.5f)
#define SUMMONER_SIZE 7.0f
#define TM_WAIT_BEFORE_FIRE 1.9f
#define SUMMONER_HEALTH 15000.0f
// info structure
static EntityInfo eiSummoner = {
  EIBT_FLESH, 1500.0f,
  0.0f, 1.7f*SUMMONER_SIZE, 0.0f,
  0.0f, 1.0f*SUMMONER_SIZE, 0.0f,
};

//#define FIREPOS_ARMS FLOAT3D(-0.22f, 1.63f, 0.96f)
#define FIREPOS_ARMS FLOAT3D(0.131292f, 1.61069f, -0.314068f)

#define SUMMONER_MAX_SS 6
                                        // hlth  grp1  grp2  grp3 
INDEX aiSpawnScheme[SUMMONER_MAX_SS][7] = {100,  4,7,  0,0,  0,0, 
                                            90,  3,5,  2,4,  0,0, 
                                            70,  3,4,  3,4,  0,0, 
                                            50,  1,3,  3,5,  1,1, 
                                            30,  1,2,  2,3,  2,2, 
                                            15,  1,1,  2,4,  2,3 };
#define SUMMONER_TEMP_PER_GROUP 6
%}


class CSummoner : CEnemyBase {
name      "Summoner";
thumbnail "Thumbnails\\Summoner.tbn";

properties:
  
   1 BOOL  m_bInvulnerable = FALSE, // can we be hurt?
   2 CEntityPointer m_penBeginDeathTarget "Sum. Begin Death Target",   
   3 CEntityPointer m_penEndDeathTarget "Sum. End Death Target",
   4 CEntityPointer m_penExplodeDeathTarget "Sum. Explode Target",
   5 BOOL  m_bShouldTeleport = FALSE, // are we allowed to teleport?
   6 FLOAT m_fFirePeriod = 3.0f,
   7 FLOAT m_fImmaterialDuration "Sum. Immaterial Duration" = 5.0f, // how long to stay immaterial
   8 FLOAT m_fCorporealDuration "Sum. Corporeal Duration" = 5.0f, // how long to stay material
   9 FLOAT m_tmMaterializationTime = 0.0f, // when we materialized

  10 FLOAT m_fStretch "Sum. Stretch" = SUMMONER_SIZE,
  11 INDEX m_iSize = 1,     // how big it is (gets bigger when harmed)
  12 CEntityPointer m_penControlArea "Sum. Control Area",
  
  // NOTE: if number of templates per group changes, you MUST change the
  // SUMMONER_TEMP_PER_GROUP #definition at the beginning of the file
  20 INDEX m_iGroup01Count = 0,
  21 CEntityPointer m_penGroup01Template01 "Sum. Group01 Template01",
  22 CEntityPointer m_penGroup01Template02 "Sum. Group01 Template02",
  23 CEntityPointer m_penGroup01Template03 "Sum. Group01 Template03",
  24 CEntityPointer m_penGroup01Template04 "Sum. Group01 Template04",
  25 CEntityPointer m_penGroup01Template05 "Sum. Group01 Template05",
  26 CEntityPointer m_penGroup01Template06 "Sum. Group01 Template06",
  
  30 INDEX m_iGroup02Count = 0,
  31 CEntityPointer m_penGroup02Template01 "Sum. Group02 Template01",
  32 CEntityPointer m_penGroup02Template02 "Sum. Group02 Template02",
  33 CEntityPointer m_penGroup02Template03 "Sum. Group02 Template03",
  34 CEntityPointer m_penGroup02Template04 "Sum. Group02 Template04",
  35 CEntityPointer m_penGroup02Template05 "Sum. Group02 Template05",
  36 CEntityPointer m_penGroup02Template06 "Sum. Group02 Template06",
  
  40 INDEX m_iGroup03Count = 0,
  41 CEntityPointer m_penGroup03Template01 "Sum. Group03 Template01",
  42 CEntityPointer m_penGroup03Template02 "Sum. Group03 Template02",
  43 CEntityPointer m_penGroup03Template03 "Sum. Group03 Template03",
  44 CEntityPointer m_penGroup03Template04 "Sum. Group03 Template04",
  45 CEntityPointer m_penGroup03Template05 "Sum. Group03 Template05",
  46 CEntityPointer m_penGroup03Template06 "Sum. Group03 Template06",
  
  60 CEntityPointer m_penTeleportMarker "Sum. Teleport marker",
  61 INDEX m_iTeleportMarkers = 0, // number of teleport markers
  65 CEntityPointer m_penSpawnMarker "Sum. Enemy spawn marker",
  66 INDEX m_iSpawnMarkers = 0, // number of spawn markers
  67 FLOAT m_fTeleportWaitTime = 0.0f, // internal

  70 FLOAT m_fFuss = 0.0f, // value of all enemies scores summed up
  78 INDEX m_iEnemyCount = 0, // how many enemies in the area
  71 FLOAT m_fMaxCurrentFuss = 0.0f,
  72 FLOAT m_fMaxBeginFuss "Sum. Max Begin Fuss" = 10000.0f, 
  73 FLOAT m_fMaxEndFuss "Sum. Max End Fuss" = 60000.0f, 
  75 INDEX m_iSpawnScheme = 0,
  76 BOOL  m_bFireOK = TRUE,
  79 BOOL  m_bFiredThisTurn = FALSE,
  77 FLOAT m_fDamageSinceLastSpawn = 0.0f,

  88 BOOL  m_bExploded = FALSE, // still alive and embodied
  90 BOOL  m_bDying = FALSE,  // set when dying
  92 FLOAT m_tmDeathBegin = 0.0f,
  93 FLOAT m_fDeathDuration = 0.0f,
  94 CEntityPointer m_penDeathInflictor,
 111 CEntityPointer m_penKiller,
  // internal variables
  95 FLOAT3D m_vDeathPosition = FLOAT3D(0.0f, 0.0f, 0.0f),
  96 CEntityPointer m_penDeathMarker "Sum. Death marker",
 100 INDEX m_iIndex = 0, // temp. index

 102 INDEX m_iTaunt = 0, // index of currently active taunt

 110 FLOAT m_tmParticlesDisappearStart=-1e6,
 
 120 FLOAT m_tmLastAnimation=0.0f,

 150 CSoundObject m_soExplosion,
 151 CSoundObject m_soSound,
 152 CSoundObject m_soChant,
 153 CSoundObject m_soTeleport,
 
{
  CEmiter m_emEmiter;
}

components:
  0 class   CLASS_BASE               "Classes\\EnemyBase.ecl",
  1 class   CLASS_BLOOD_SPRAY        "Classes\\BloodSpray.ecl",
  2 class   CLASS_SPAWNER_PROJECTILE "Classes\\SpawnerProjectile.ecl",
  3 class   CLASS_BASIC_EFFECT       "Classes\\BasicEffect.ecl",
  4 class   CLASS_EFFECTOR           "Classes\\Effector.ecl",
  
// ************** MAIN MODEL **************

 10 model   MODEL_SUMMONER      "ModelsMP\\Enemies\\Summoner\\Summoner.mdl",
 11 texture TEXTURE_SUMMONER    "ModelsMP\\Enemies\\Summoner\\Summoner.tex",
 12 model   MODEL_STAFF         "ModelsMP\\Enemies\\Summoner\\Staff.mdl",
 13 texture TEXTURE_STAFF       "ModelsMP\\Enemies\\Summoner\\Staff.tex",
 
 16 model   MODEL_DEBRIS01      "ModelsMP\\Enemies\\Summoner\\Debris\\Cloth01.mdl",
 17 model   MODEL_DEBRIS02      "ModelsMP\\Enemies\\Summoner\\Debris\\Cloth02.mdl",
 18 model   MODEL_DEBRIS03      "ModelsMP\\Enemies\\Summoner\\Debris\\Cloth03.mdl",
 19 model   MODEL_DEBRIS_FLESH  "Models\\Effects\\Debris\\Flesh\\Flesh.mdl",
 20 texture TEXTURE_DEBRIS_FLESH  "Models\\Effects\\Debris\\Flesh\\FleshRed.tex",


// ************** SOUNDS **************
//200 sound   SOUND_IDLE   "ModelsMP\\Enemies\\Summoner\\Sounds\\Idle.wav",
101 sound  SOUND_LAUGH      "ModelsMP\\Enemies\\Summoner\\Sounds\\Laugh.wav",
102 sound  SOUND_EXPLODE    "ModelsMP\\Enemies\\Summoner\\Sounds\\Explode.wav",
103 sound  SOUND_TREMORS    "ModelsMP\\Enemies\\Summoner\\Sounds\\Tremors.wav",
104 sound  SOUND_DEATH      "ModelsMP\\Enemies\\Summoner\\Sounds\\Death.wav",
105 sound  SOUND_LASTWORDS  "ModelsMP\\Enemies\\Summoner\\Sounds\\LastWords.wav",
106 sound  SOUND_FIRE       "ModelsMP\\Enemies\\Summoner\\Sounds\\Fire.wav",
108 sound  SOUND_CHIMES     "ModelsMP\\Enemies\\Summoner\\Sounds\\Chimes.wav",
107 sound  SOUND_MATERIALIZE "ModelsMP\\Enemies\\Summoner\\Sounds\\Materialize.wav",
109 sound  SOUND_TELEPORT    "ModelsMP\\Enemies\\Summoner\\Sounds\\Teleport.wav",

// ***** TAUNTS ******
150 sound  SOUND_TAUNT01    "ModelsMP\\Enemies\\Summoner\\Sounds\\Quote03.wav",
151 sound  SOUND_TAUNT02    "ModelsMP\\Enemies\\Summoner\\Sounds\\Quote05.wav",
152 sound  SOUND_TAUNT03    "ModelsMP\\Enemies\\Summoner\\Sounds\\Quote07.wav",
153 sound  SOUND_TAUNT04    "ModelsMP\\Enemies\\Summoner\\Sounds\\Quote08.wav",
154 sound  SOUND_TAUNT05    "ModelsMP\\Enemies\\Summoner\\Sounds\\Quote10.wav",
155 sound  SOUND_TAUNT06    "ModelsMP\\Enemies\\Summoner\\Sounds\\Quote11.wav",
156 sound  SOUND_TAUNT07    "ModelsMP\\Enemies\\Summoner\\Sounds\\Quote14.wav",
157 sound  SOUND_TAUNT08    "ModelsMP\\Enemies\\Summoner\\Sounds\\Quote15.wav",
158 sound  SOUND_TAUNTLAST  "ModelsMP\\Enemies\\Summoner\\Sounds\\Quote16.wav",

functions:
  void Read_t( CTStream *istr) // throw char *
  { 
    CEnemyBase::Read_t(istr);
    m_emEmiter.Read_t(*istr);
  }
  
  void Write_t( CTStream *istr) // throw char *
  { 
    CEnemyBase::Write_t(istr);
    m_emEmiter.Write_t(*istr);
  }
 
  BOOL IsTargetValid(SLONG slPropertyOffset, CEntity *penTarget)
  {
    if ( slPropertyOffset >= offsetof(CSummoner, m_penGroup01Template01) &&
      slPropertyOffset <= offsetof(CSummoner, m_penGroup03Template06))
    {
      if (IsDerivedFromClass(penTarget, "Enemy Base")) {
        if (((CEnemyBase &)*penTarget).m_bTemplate) {
          return TRUE;
        } else {
          return FALSE;
        }
      } else {
        return FALSE; 
      }
    }
    if( slPropertyOffset == offsetof(CSummoner, m_penControlArea))
    {
      if (IsDerivedFromClass(penTarget, "AreaMarker")) {
        return TRUE;
      } else {
        return FALSE;
      }
    }
    if( slPropertyOffset == offsetof(CSummoner, m_penSpawnMarker))
    {
      if (IsDerivedFromClass(penTarget, "Enemy Marker")) {
        return TRUE;
      } else {
        return FALSE;
      }
    }
    if( slPropertyOffset == offsetof(CSummoner, m_penTeleportMarker) ||
        slPropertyOffset == offsetof(CSummoner, m_penDeathMarker))
    {
      if (IsDerivedFromClass(penTarget, "SummonerMarker")) {
        return TRUE;
      } else {
        return FALSE;
      }
    }
    return CEntity::IsTargetValid(slPropertyOffset, penTarget);    
  }
  
  
  BOOL DoSafetyChecks(void) {
    
    if (m_penSpawnMarker==NULL) {
      WarningMessage( "No valid Spawn Marker for Summoner boss! Destroying boss...");
      return FALSE;
    }
    if (m_penTeleportMarker==NULL) {
      WarningMessage( "No valid Teleport Marker for Summoner boss! Destroying boss...");
      return FALSE;
    }
    if (m_penDeathMarker==NULL) {
      WarningMessage( "No valid Death Marker for Summoner boss! Destroying boss...");
      return FALSE;
    }
    if (m_penControlArea==NULL) {
      WarningMessage( "No valid Area Marker for Summoner boss! Destroying boss...");
      return FALSE;
    }
    if (m_iGroup01Count<1 || m_iGroup02Count<1 || m_iGroup03Count<1)
    {
      WarningMessage( "At least one template in each group required! Destroying boss...");
      return FALSE;
    }
    return TRUE;
  }

  // describe how this enemy killed player
  virtual CTString GetPlayerKillDescription(const CTString &strPlayerName, const EDeath &eDeath)
  {
    CTString str;
    str.PrintF(TRANS("The Summoner unsummoned %s"), strPlayerName);
    return str;
  }
  
  virtual const CTFileName &GetComputerMessageName(void) const {
    static DECLARE_CTFILENAME(fnm, "DataMP\\Messages\\Enemies\\Summoner.txt");
    return fnm;
  };

  void Precache(void)
  {
    CEnemyBase::Precache();
    
    PrecacheClass(CLASS_BLOOD_SPRAY        );
    PrecacheClass(CLASS_SPAWNER_PROJECTILE );

    PrecacheClass(CLASS_BASIC_EFFECT, BET_CANNON );
    
    PrecacheModel(MODEL_SUMMONER     );
    PrecacheModel(MODEL_STAFF        );
    PrecacheTexture(TEXTURE_SUMMONER );
    PrecacheTexture(TEXTURE_STAFF    );
  
    PrecacheModel(MODEL_DEBRIS01     );
    PrecacheModel(MODEL_DEBRIS02     );
    PrecacheModel(MODEL_DEBRIS03     );
    PrecacheModel(MODEL_DEBRIS_FLESH );
    PrecacheTexture(TEXTURE_DEBRIS_FLESH );

    PrecacheSound(SOUND_LAUGH       );
    PrecacheSound(SOUND_EXPLODE     );
    PrecacheSound(SOUND_TREMORS     );
    PrecacheSound(SOUND_DEATH       );
    PrecacheSound(SOUND_LASTWORDS   );
    PrecacheSound(SOUND_FIRE        );
    PrecacheSound(SOUND_CHIMES      );
    PrecacheSound(SOUND_MATERIALIZE );
    PrecacheSound(SOUND_TELEPORT    );
  
    for (INDEX i=SOUND_TAUNT01; i<=SOUND_TAUNTLAST; i++) { 
      PrecacheSound(i); 
    }
  
  };


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

  CMusicHolder *GetMusicHolder()
  {
    CEntity *penMusicHolder;
    penMusicHolder = _pNetwork->GetEntityWithName("MusicHolder", 0);
    return (CMusicHolder *)&*penMusicHolder;
  }

  BOOL DistanceToAllPlayersGreaterThen(FLOAT fDistance)
  {
    // find actual number of players
    INDEX ctMaxPlayers = GetMaxPlayers();
    CEntity *penPlayer;
    
    for(INDEX i=0; i<ctMaxPlayers; i++) {
      penPlayer=GetPlayerEntity(i);
      if (penPlayer) {
        if (DistanceTo(this, penPlayer)<fDistance) {
          return FALSE;
        }
      }
    }
    return TRUE;
  };

  /* Shake ground */
  void ShakeItBaby(FLOAT tmShaketime, FLOAT fPower, BOOL bFadeIn)
  {
    CWorldSettingsController *pwsc = GetWSC(this);
    if (pwsc!=NULL) {
      pwsc->m_tmShakeStarted = tmShaketime;
      pwsc->m_vShakePos = GetPlacement().pl_PositionVector;
      pwsc->m_fShakeFalloff = 450.0f;
      pwsc->m_fShakeFade = 3.0f;

      pwsc->m_fShakeIntensityZ = 0;
      pwsc->m_tmShakeFrequencyZ = 5.0f;
      pwsc->m_fShakeIntensityY = 0.1f*fPower;
      pwsc->m_tmShakeFrequencyY = 5.0f;
      pwsc->m_fShakeIntensityB = 2.5f*fPower;
      pwsc->m_tmShakeFrequencyB = 7.2f;

      pwsc->m_bShakeFadeIn = bFadeIn;
    }
  }

  void ChangeEnemyNumberForAllPlayers(INDEX iDelta)
  {
    // find actual number of players
    INDEX ctMaxPlayers = GetMaxPlayers();
    CEntity *penPlayer;
    
    for(INDEX i=0; i<ctMaxPlayers; i++) {
      penPlayer=GetPlayerEntity(i);
      if (penPlayer) {
        // set totals for level and increment for game
        ((CPlayer &)*penPlayer).m_psLevelTotal.ps_iKills+=iDelta;
        ((CPlayer &)*penPlayer).m_psGameTotal.ps_iKills+=iDelta;
      }
    }
  };

  // Receive damage
  void ReceiveDamage(CEntity *penInflictor, enum DamageType dmtType,
    FLOAT fDamageAmmount, const FLOAT3D &vHitPoint, const FLOAT3D &vDirection) 
  {
    
    // while we are invulnerable, receive no damage
    if (m_bInvulnerable) {
      return;
    }

    // summoner doesn't receive damage from other monsters
    if(!IsOfClass(penInflictor, "Player")) {
      return;
    }

    // boss cannot be telefragged
    if(dmtType==DMT_TELEPORT)
    {
      return;
    }

    // cannonballs inflict less damage then the default
    if(dmtType==DMT_CANNONBALL)
    {
      fDamageAmmount *= 0.5f;
    }
    
    FLOAT fOldHealth = GetHealth();
    CEnemyBase::ReceiveDamage(penInflictor, dmtType, fDamageAmmount, vHitPoint, vDirection);
    FLOAT fNewHealth = GetHealth();

    // increase temp. damage 
    m_fDamageSinceLastSpawn += fOldHealth - fNewHealth;

    // see if we have to change the spawning scheme
    for (INDEX i=0; i<SUMMONER_MAX_SS; i++) {
      FLOAT fHealth = (FLOAT)aiSpawnScheme[i][0]*m_fMaxHealth/100.0f;
      if (fHealth<=fOldHealth && fHealth>fNewHealth)
      {
        m_iSpawnScheme = i;
      }
    }
    
    // adjust fuss
    m_fMaxCurrentFuss = (1.0f-(GetHealth()/m_fMaxHealth))*(m_fMaxEndFuss-m_fMaxBeginFuss)+m_fMaxBeginFuss;
    
    // bosses don't darken when burning
    m_colBurning=COLOR(C_WHITE|CT_OPAQUE);

  };


  // damage anim
  /*INDEX AnimForDamage(FLOAT fDamage) {
    INDEX iAnim;
    iAnim = SUMMONER_ANIM_WOUND;
    StartModelAnim(iAnim, 0);
    return iAnim;
  };*/

  void StandingAnimFight(void) {
    StartModelAnim(SUMMONER_ANIM_IDLE, AOF_LOOPING|AOF_NORESTART);
  };

  // virtual anim functions
  void StandingAnim(void) {
    StartModelAnim(SUMMONER_ANIM_IDLE, AOF_LOOPING|AOF_NORESTART);        
  };

  void WalkingAnim(void) {
    StartModelAnim(SUMMONER_ANIM_IDLE, AOF_LOOPING|AOF_NORESTART);
  };

  void RunningAnim(void) {
    WalkingAnim();
  };

  void RotatingAnim(void) {
    WalkingAnim();
  };

  /*INDEX AnimForDeath(void) {
    INDEX iAnim;
    iAnim = SUMMONER_ANIM_DEATH;
    StartModelAnim(iAnim, 0);
    return iAnim;
  };*/

  // virtual sound functions
  void IdleSound(void) {
    //PlaySound(m_soSound, SOUND_IDLE, SOF_3D);
  };
  
  FLOAT3D AcquireTarget()
  {
    CEnemyMarker *marker;
    marker = &((CEnemyMarker &)*m_penSpawnMarker);
    
    INDEX iMarker = IRnd()%m_iSpawnMarkers;

    while (iMarker>0)
    {
      marker = &((CEnemyMarker &)*marker->m_penTarget);
      iMarker--;
    }
    FLOAT3D vTarget = marker->GetPlacement().pl_PositionVector;
    FLOAT fR = FRnd()*marker->m_fMarkerRange;
    FLOAT fA = FRnd()*360.0f;
    vTarget += FLOAT3D(CosFast(fA)*fR, 0.05f, SinFast(fA)*fR);
    return vTarget;
  };

  void LaunchMonster(FLOAT3D vTarget, CEntity *penTemplate)
  {
    ASSERT(penTemplate!=NULL);
    // calculate parameters for predicted angular launch curve
    FLOAT3D vFirePos = FIREPOS_ARMS*m_fStretch;
    FLOAT3D vShooting = GetPlacement().pl_PositionVector + vFirePos*GetRotationMatrix();
    FLOAT fLaunchSpeed;
    FLOAT fRelativeHdg;
    //FLOAT fPitch = FRnd()*30.0f - 5.0f;
    FLOAT fPitch = FRnd()*10.0f + 25.0f;

    CPlacement3D pl;
    CalculateAngularLaunchParams( vShooting, 0.0f, vTarget,
      FLOAT3D(0.0f, 0.0f, 0.0f), fPitch, fLaunchSpeed, fRelativeHdg);
    
    PrepareFreeFlyingProjectile(pl, vTarget, vFirePos, ANGLE3D( fRelativeHdg, fPitch, 0.0f));
    
    ESpawnerProjectile esp;
    CEntityPointer penSProjectile = CreateEntity(pl, CLASS_SPAWNER_PROJECTILE);
    esp.penOwner = this;
    esp.penTemplate = penTemplate;
    penSProjectile->Initialize(esp);
    
    ((CMovableEntity &)*penSProjectile).LaunchAsFreeProjectile(FLOAT3D(0.0f, 0.0f, -fLaunchSpeed), (CMovableEntity*)(CEntity*)this);
  }
 
  FLOAT FussModifier(INDEX iEnemyCount) {
    return (0.995 + 0.005 * pow(m_iEnemyCount , 2.8));
  }

  void RecalculateFuss(void)
  {
    // get area box
    FLOATaabbox3D box;
    ((CAreaMarker &)*m_penControlArea).GetAreaBox(box);
    
    static CStaticStackArray<CEntity *> apenNearEntities;
    GetWorld()->FindEntitiesNearBox(box, apenNearEntities);

    INDEX m_iEnemyCount = 0;
    m_fFuss = 0.0f;
  
    for (INDEX i=0; i<apenNearEntities.Count(); i++)
    {
      if (IsDerivedFromClass(apenNearEntities[i], "Enemy Base") &&
        !IsOfClass(apenNearEntities[i], "Summoner")) {
        if (!((CEnemyBase &)*apenNearEntities[i]).m_bTemplate &&
          apenNearEntities[i]->GetFlags()&ENF_ALIVE) {
          m_fFuss += ((CEnemyBase &)*apenNearEntities[i]).m_iScore;
          m_iEnemyCount  ++;
        }
      }
    }

    m_fFuss *= FussModifier(m_iEnemyCount);

    // if too much fuss, make disable firing
    if (m_fFuss>m_fMaxCurrentFuss) {
//CPrintF("FIRE DISABLED -> too much fuss\n");
      m_bFireOK = FALSE;
    // enable firing only when very little fuss
    } else if (m_fFuss<0.4*m_fMaxCurrentFuss) {
//CPrintF("FIRE ENABLE -> fuss more then %f\n", 0.4*m_fMaxCurrentFuss);
      m_bFireOK = TRUE; 
    // but if significant damage since last spawn, enable firing anyway
    } else if (m_fDamageSinceLastSpawn>0.07f*m_fMaxHealth) {
//CPrintF("FIRE ENABLED -> much damagesincelastspawn - %f\n", m_fDamageSinceLastSpawn);
      m_bFireOK = TRUE;
    }

//CPrintF("Fuss = %f/%f (%d enemies) at %f\n", m_fFuss, m_fMaxCurrentFuss, m_iEnemyCount, _pTimer->CurrentTick());
//CPrintF("health: %f, scheme: %i\n", GetHealth(), m_iSpawnScheme);
    return;
  }

  void CountEnemiesAndScoreValue(INDEX& iEnemies, FLOAT& fScore)
  {
    // get area box
    FLOATaabbox3D box;
    ((CAreaMarker &)*m_penControlArea).GetAreaBox(box);
    
    static CStaticStackArray<CEntity *> apenNearEntities;
    GetWorld()->FindEntitiesNearBox(box, apenNearEntities);
    
    iEnemies = 0;
    fScore = 0.0f;

    for (INDEX i=0; i<apenNearEntities.Count(); i++)
    {
      if (IsDerivedFromClass(apenNearEntities[i], "Enemy Base") &&
        !IsOfClass(apenNearEntities[i], "Summoner")) {
        if (!((CEnemyBase &)*apenNearEntities[i]).m_bTemplate &&
          apenNearEntities[i]->GetFlags()&ENF_ALIVE) {
          fScore += ((CEnemyBase &)*apenNearEntities[i]).m_iScore;
          iEnemies  ++;
        }
      }
    }
    return;
  }

  CEnemyBase *GetRandomTemplate (INDEX iGroup)
  {
    CEntityPointer *pen;
    INDEX iCount;
    if (iGroup == 0) {
      pen = &m_penGroup01Template01;
      iCount = IRnd()%m_iGroup01Count+1;
    } else if (iGroup == 1) {
      pen = &m_penGroup02Template01;
      iCount = IRnd()%m_iGroup02Count+1;
    } else if (iGroup == 2) {
      pen = &m_penGroup03Template01;
      iCount = IRnd()%m_iGroup03Count+1;
    } else {
      ASSERT("Invalid group!");
    }
    ASSERT(iCount>0);

    INDEX i=-1;
    while (iCount>0)
    {
      i++;
      while (&*pen[i]==NULL) {
        i++;
      } 
      iCount--;        
    }
    ASSERT (&(CEnemyBase &)*pen[i]!=NULL);
    return &(CEnemyBase &)*pen[i];
  }

  void DisappearEffect(void)
  {
    CPlacement3D plFX=GetPlacement();
    ESpawnEffect ese;
    ese.colMuliplier = C_WHITE|CT_OPAQUE;
    ese.vStretch = FLOAT3D(3,3,3);
    ese.vNormal = FLOAT3D(0,1,0);
    ese.betType = BET_DUST_FALL;
    for( INDEX iSmoke=0; iSmoke<3; iSmoke++)
    {
      CPlacement3D plSmoke=plFX;
      plSmoke.pl_PositionVector+=FLOAT3D(0,iSmoke*4+4.0f,0);
      CEntityPointer penFX = CreateEntity(plSmoke, CLASS_BASIC_EFFECT);
      penFX->Initialize(ese);
    }

    /*
    // growing swirl
    ese.betType = BET_DISAPPEAR_DUST;
    penFX = CreateEntity(plFX, CLASS_BASIC_EFFECT);
    penFX->Initialize(ese);
    */
  }

  void SpawnTeleportEffect(void)
  {
    ESpawnEffect ese;
    ese.colMuliplier = C_lMAGENTA|CT_OPAQUE;
    ese.vStretch = FLOAT3D(5,5,5);
    ese.vNormal = FLOAT3D(0,1,0);

    // explosion debris
    ese.betType = BET_EXPLOSION_DEBRIS;
    CPlacement3D plFX=GetPlacement();
    CEntityPointer penFX = CreateEntity(plFX, CLASS_BASIC_EFFECT);
    penFX->Initialize(ese);
    ese.colMuliplier = C_MAGENTA|CT_OPAQUE;
    CEntityPointer penFX2 = CreateEntity(plFX, CLASS_BASIC_EFFECT);
    penFX2->Initialize(ese);
    ese.colMuliplier = C_lCYAN|CT_OPAQUE;
    CEntityPointer penFX3 = CreateEntity(plFX, CLASS_BASIC_EFFECT);
    penFX3->Initialize(ese);
    ese.betType = BET_CANNON;
    ese.colMuliplier = C_CYAN|CT_OPAQUE;
    CEntityPointer penFX4 = CreateEntity(plFX, CLASS_BASIC_EFFECT);
    penFX4->Initialize(ese);

    // explosion smoke
    /*
    ese.betType = BET_EXPLOSION_SMOKE;
    penFX = CreateEntity(plFX, CLASS_BASIC_EFFECT);
    penFX->Initialize(ese);
    */

    ESpawnEffector eLightning;
    eLightning.eetType = ET_LIGHTNING;
    eLightning.tmLifeTime = 0.5f;
    eLightning.fSize = 24;
    eLightning.ctCount = 32;

    CEntity *penLightning = CreateEntity( plFX, CLASS_EFFECTOR);
    ANGLE3D angRnd=ANGLE3D(
      0.0f,
      90.0f+(FRnd()-0.5f)*30.0f,
      (FRnd()-0.5f)*30.0f);

    FLOAT3D vRndDir;
    AnglesToDirectionVector(angRnd, vRndDir);
    FLOAT3D vDest=plFX.pl_PositionVector;
    vDest+=vRndDir*512.0f;
    eLightning.vDestination = vDest; 
    penLightning->Initialize( eLightning);
  }

  void KillAllEnemiesInArea(EDeath eDeath)
  {
    EDeath eDeath2;
    FLOATaabbox3D box;
    ((CAreaMarker &)*m_penControlArea).GetAreaBox(box);
    
    static CStaticStackArray<CEntity *> apenNearEntities;
    GetWorld()->FindEntitiesNearBox(box, apenNearEntities);
    
    for (INDEX i=0; i<apenNearEntities.Count(); i++)
    {
      if (IsDerivedFromClass(apenNearEntities[i], "Enemy Base") &&
        !IsOfClass(apenNearEntities[i], "Summoner")) {
        if (!((CEnemyBase &)*apenNearEntities[i]).m_bTemplate &&
          apenNearEntities[i]->GetFlags()&ENF_ALIVE) {
          eDeath2.eLastDamage.penInflictor = eDeath.eLastDamage.penInflictor;
          eDeath2.eLastDamage.vDirection = apenNearEntities[i]->GetPlacement().pl_PositionVector;
          eDeath2.eLastDamage.vHitPoint = eDeath2.eLastDamage.vDirection;
          eDeath2.eLastDamage.fAmount = 10000.0f;
          eDeath2.eLastDamage.dmtType = DMT_CLOSERANGE;
          apenNearEntities[i]->SendEvent(eDeath);
        }
      }
 
      CMusicHolder *penMusicHolder = GetMusicHolder();
      if (IsOfClass(apenNearEntities[i],"SpawnerProjectile")) {
        CPlacement3D pl;
        pl.pl_OrientationAngle = ANGLE3D(0.0f, 0.0f, 0.0f);
        pl.pl_PositionVector = apenNearEntities[i]->GetPlacement().pl_PositionVector;
        CEntityPointer penExplosion = CreateEntity(pl, CLASS_BASIC_EFFECT);
        ESpawnEffect eSpawnEffect;
        eSpawnEffect.colMuliplier = C_WHITE|CT_OPAQUE;
        eSpawnEffect.betType = BET_CANNON;
        eSpawnEffect.vStretch = FLOAT3D(2.0f, 2.0f, 2.0f);
        penExplosion->Initialize(eSpawnEffect);
        apenNearEntities[i]->Destroy();
        // decrease the number of spawned enemies for those that haven't been born
        if (penMusicHolder!=NULL) {          
          penMusicHolder->m_ctEnemiesInWorld--;
        }          
        ChangeEnemyNumberForAllPlayers(-1);
      }
    }    
  }

  void RenderParticles(void)
  {
    FLOAT tmNow = _pTimer->CurrentTick();
    if( tmNow>m_tmParticlesDisappearStart && tmNow<m_tmParticlesDisappearStart+4.0f)
    {
      Particles_SummonerDisappear(this, m_tmParticlesDisappearStart);
    }

    if( tmNow>m_tmLastAnimation)
    {
      INDEX ctInterpolations=2;
      // if invisible or dead don't add new sparks
      if(!m_bInvulnerable && !m_bExploded && GetHealth()>0)
      {
        for( INDEX iInter=0; iInter<ctInterpolations; iInter++)
        {
          // for holding attachment data
          FLOATmatrix3D mEn=GetRotationMatrix();
          FLOATmatrix3D mRot;
          FLOAT3D vPos;
          FLOAT tmBirth=tmNow+iInter*_pTimer->TickQuantum/ctInterpolations;
          // head
          FLOAT fLife=2.5f;
          FLOAT fCone=360.0f;
          FLOAT fStretch=1.0f;
          FLOAT fRotSpeed=360.0f;
          COLOR col=C_lYELLOW|CT_OPAQUE;

          MakeRotationMatrixFast(mRot, ANGLE3D(0.0f, 0.0f, 0.0f));
          vPos=FLOAT3D(0.0f, 0.0f, 0.0f);
          GetModelObject()->GetAttachmentTransformations(SUMMONER_ATTACHMENT_STAFF, mRot, vPos, FALSE);
          // next in hierarchy
          CAttachmentModelObject *pamo = GetModelObject()->GetAttachmentModel(SUMMONER_ATTACHMENT_STAFF);
          pamo->amo_moModelObject.GetAttachmentTransformations( STAFF_ATTACHMENT_PARTICLES, mRot, vPos, TRUE);
          vPos=GetPlacement().pl_PositionVector+vPos*GetRotationMatrix();

          FLOAT3D vSpeed=FLOAT3D( 0.1f+RAND_05, 0.1f+RAND_05, -1.0f-RAND_05);
          vSpeed=vSpeed.Normalize()*8.0f;
          m_emEmiter.AddParticle(vPos, vSpeed*mRot*mEn, RAND_05*360.0f, fRotSpeed, tmBirth, fLife, fStretch, col);
        }
      }

      m_emEmiter.em_vG=m_emEmiter.GetGravity(this);
      m_emEmiter.em_vG/=2.0f;
      m_emEmiter.AnimateParticles();
      m_tmLastAnimation=tmNow;
    }
    m_emEmiter.RenderParticles();
  }
  

procedures:
  
  InitiateTeleport()
  {
    m_bInvulnerable = TRUE;
    StartModelAnim(SUMMONER_ANIM_VANISHING, 0);

    // start disappear particles
    FLOAT tmNow = _pTimer->CurrentTick();
    m_tmParticlesDisappearStart=tmNow;

    PlaySound(m_soSound, SOUND_TELEPORT, SOF_3D);

    autowait(GetModelObject()->GetAnimLength(SUMMONER_ANIM_VANISHING)-0.2f);
    jump Immaterial();
  }
  
  Fire(EVoid) : CEnemyBase::Fire {
    
    // if not allready fired
    if (!m_bFiredThisTurn) {
      // if not too much fuss, we can really fire
      if (m_bFireOK) {
                
        INDEX iTaunt = SOUND_TAUNT01 + m_iTaunt%(SOUND_TAUNTLAST-SOUND_TAUNT01+1);
        PlaySound(m_soChant, iTaunt, SOF_3D);
        m_iTaunt++;

        StartModelAnim(SUMMONER_ANIM_MAGICATTACK, SOF_SMOOTHCHANGE);
        
        //wait to get into spawner emitting position
        autowait(TM_WAIT_BEFORE_FIRE);
        PlaySound(m_soSound, SOUND_FIRE, SOF_3D);

        INDEX i,j;
        FLOAT3D vTarget;
        FLOAT fTotalSpawnedScore = 0.0f;
        INDEX iTotalSpawnedCount = 0;
        INDEX iEnemyCount;
        FLOAT fScore;
        CountEnemiesAndScoreValue(iEnemyCount, fScore);
        
        CMusicHolder *penMusicHolder = GetMusicHolder();
        
        FLOAT fTmpFuss = 0.0f;
        
        // for each group in current spawn scheme
        for (i=0; i<3; i++) {
          INDEX iMin = aiSpawnScheme[m_iSpawnScheme][i*2+1];
          INDEX iMax = aiSpawnScheme[m_iSpawnScheme][i*2+2];
          ASSERT(iMin<=iMax);
          INDEX iToSpawn;
          iToSpawn = iMin + IRnd()%(iMax - iMin + 1);
          for (j=0; j<iToSpawn; j++) {
            CEnemyBase *penTemplate = GetRandomTemplate(i);
            vTarget = AcquireTarget();
            LaunchMonster(vTarget, penTemplate);
            fTotalSpawnedScore+= penTemplate->m_iScore;
            iTotalSpawnedCount++;
            // increase the number of spawned enemies in music holder
            if (penMusicHolder!=NULL) {
              penMusicHolder->m_ctEnemiesInWorld++;
            }
            ChangeEnemyNumberForAllPlayers(+1);
            // stop spawning if too much fuss or too many enemies
            fTmpFuss = (fTotalSpawnedScore+fScore)*FussModifier(iTotalSpawnedCount+iEnemyCount);
            if (fTmpFuss>m_fMaxCurrentFuss) { 
              break; 
            }
          }
        }
        
        m_fDamageSinceLastSpawn = 0.0f;
                
        //wait for firing animation to stop
        autowait(GetModelObject()->GetAnimLength(SUMMONER_ANIM_MAGICATTACK)-TM_WAIT_BEFORE_FIRE);
        
        // stand a while
        StartModelAnim(SUMMONER_ANIM_IDLE, SOF_SMOOTHCHANGE);
        
        // teleport by sending an event to ourselves
        ESummonerTeleport est;
        est.fWait = FRnd()*1.0f+3.0f;
        SendEvent(est);
      // if too much fuss, just laugh and initiate teleport
      } else if (TRUE) {
      
        PlaySound(m_soExplosion, SOUND_LAUGH, SOF_3D);
        autowait(1.0f);
        StartModelAnim(SUMMONER_ANIM_MAGICATTACK, SOF_SMOOTHCHANGE);
        
        INDEX iTaunt = SOUND_TAUNT01 + m_iTaunt%(SOUND_TAUNTLAST-SOUND_TAUNT01+1);
        PlaySound(m_soChant, iTaunt, SOF_3D);
        m_iTaunt++;
        
        //wait to get into spawner emitting position
        autowait(TM_WAIT_BEFORE_FIRE);
        PlaySound(m_soSound, SOUND_FIRE, SOF_3D);

        INDEX iEnemyCount;
        FLOAT fScore;
        CountEnemiesAndScoreValue(iEnemyCount, fScore);
        FLOAT fToSpawn;

        INDEX iScheme;
        // find last active scheme
        for (INDEX i=0; i<3; i++) {
          if (aiSpawnScheme[m_iSpawnScheme][i*2+2]>0) {
            iScheme = i;
          }
        }
        
        if (m_iSpawnScheme>3) {
          iEnemyCount += (m_iSpawnScheme-3);
        }
        
        if (iEnemyCount<6) {
          fToSpawn = (6.0 - (FLOAT)iEnemyCount)/2.0f;
        } else {
          fToSpawn = 1.0f;
        }
        INDEX iToSpawn = ceilf(fToSpawn);
        
        CMusicHolder *penMusicHolder = GetMusicHolder();
//CPrintF("spawning %d from %d group\n", iToSpawn, iScheme);
        // spawn
        for (INDEX j=0; j<iToSpawn; j++) {
          CEnemyBase *penTemplate = GetRandomTemplate(iScheme);
          FLOAT3D vTarget = AcquireTarget();
          LaunchMonster(vTarget, penTemplate);
          // increase the number of spawned enemies in music holder
          if (penMusicHolder!=NULL) {
            penMusicHolder->m_ctEnemiesInWorld++;
          }
          ChangeEnemyNumberForAllPlayers(+1);
        }

        // teleport by sending an event to ourselves
        ESummonerTeleport est;
        est.fWait = FRnd()*1.0f+3.0f;
        SendEvent(est);
        // laugh
        autowait(1.0f);
        
      }

      m_bFiredThisTurn = TRUE;
    }

    return EReturn();
  };
      
  Hit(EVoid) : CEnemyBase::Hit {
    jump Fire();
    return EReturn();
  };

/************************************************************
 *                    D  E  A  T  H                         *
 ************************************************************/
  
  Die(EDeath eDeath) : CEnemyBase::Die
  {
  
    m_bDying = TRUE;

    m_penDeathInflictor = eDeath.eLastDamage.penInflictor;

    // find the one who killed, or other best suitable player
    m_penKiller = m_penDeathInflictor;
    if (m_penKiller ==NULL || !IsOfClass(m_penKiller, "Player")) {
      m_penKiller = m_penEnemy;
    }

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

    // stop rotations
    SetDesiredRotation(ANGLE3D(0.0f, 0.0f, 0.0f));
    // first kill off all enemies inside the control area
    KillAllEnemiesInArea(eDeath);

    StartModelAnim(SUMMONER_ANIM_CHANTING, SOF_SMOOTHCHANGE);
    PlaySound(m_soExplosion, SOUND_LASTWORDS, SOF_3D);
    autowait(4.0f);

    autocall TeleportToDeathMarker() EReturn;

    // do this once more, just in case anything survived
    EDeath eDeath;
    eDeath.eLastDamage.penInflictor = m_penDeathInflictor;
    KillAllEnemiesInArea(eDeath);

    // slowly start shaking
    ShakeItBaby(_pTimer->CurrentTick(), 0.25f, TRUE);
    PlaySound(m_soExplosion, SOUND_TREMORS, SOF_3D);

    m_vDeathPosition = GetPlacement().pl_PositionVector;

    // notify possible targets of beginning of the death sequence
    if (m_penBeginDeathTarget!=NULL) {
      SendToTarget(m_penBeginDeathTarget, EET_TRIGGER, m_penKiller);
    }
    
    PlaySound(m_soSound, SOUND_DEATH, SOF_3D);
    StartModelAnim(SUMMONER_ANIM_DEATHBLOW, SOF_SMOOTHCHANGE);
    autowait(GetModelObject()->GetAnimLength(SUMMONER_ANIM_DEATHBLOW)-0.25f);    

    // hide model
    SwitchToEditorModel();

    // start death starts
    CPlacement3D plStars;
    plStars = GetPlacement();
    ESpawnEffect eSpawnEffect;
    eSpawnEffect.betType = BET_SUMMONERSTAREXPLOSION;
    eSpawnEffect.colMuliplier = C_WHITE|CT_OPAQUE;
    CEntityPointer penStars = CreateEntity(plStars, CLASS_BASIC_EFFECT);
    penStars->Initialize(eSpawnEffect);

    m_tmDeathBegin = _pTimer->CurrentTick();
    m_fDeathDuration = 12.0f;
    m_bExploded = TRUE;
    ShakeItBaby(_pTimer->CurrentTick(), 5.0f, FALSE);

    PlaySound(m_soExplosion, SOUND_EXPLODE, SOF_3D);

    // spawn debris
    Debris_Begin(EIBT_FLESH, DPT_BLOODTRAIL, BET_BLOODSTAIN, 1.0f, 
      FLOAT3D(0.0f, 10.0f, 0.0f), FLOAT3D(0.0f, 0.0f, 0.0f), 5.0f, 2.0f);
    for (INDEX i=0; i<15; i++) {
      
      FLOAT3D vSpeed = FLOAT3D(0.3f+FRnd()*0.1f, 1.0f+FRnd()*0.5f, 0.3f+FRnd()*0.1f)*1.5f*m_fStretch;
      FLOAT3D vPos = vSpeed + GetPlacement().pl_PositionVector;
      ANGLE3D aAng = ANGLE3D(FRnd()*360.0f, FRnd()*360.0f, FRnd()*360.0f);
      
      vSpeed.Normalize();
      vSpeed(2) *= vSpeed(2);

      CPlacement3D plPos = CPlacement3D (vPos, aAng);
      
      switch(i%3) {
      case 0:
        Debris_Spawn_Independent(this, this, MODEL_DEBRIS01, TEXTURE_SUMMONER, 0, 0, 0, 0, m_fStretch, 
          plPos, vSpeed*70.0f, aAng);
        Debris_Spawn_Independent(this, this, MODEL_DEBRIS_FLESH, TEXTURE_DEBRIS_FLESH , 0, 0, 0, 0, m_fStretch*0.33f, 
          plPos, vSpeed*70.0f, aAng);        
        break;
      case 1:
        Debris_Spawn_Independent(this, this, MODEL_DEBRIS02, TEXTURE_SUMMONER, 0, 0, 0, 0, m_fStretch, 
          plPos, vSpeed*70.0f, aAng);
        Debris_Spawn_Independent(this, this, MODEL_DEBRIS_FLESH, TEXTURE_DEBRIS_FLESH , 0, 0, 0, 0, m_fStretch*0.33f, 
          plPos, vSpeed*70.0f, aAng);        
        break;
      case 2:
        Debris_Spawn_Independent(this, this, MODEL_DEBRIS03, TEXTURE_SUMMONER, 0, 0, 0, 0, m_fStretch, 
          plPos, vSpeed*70.0f, aAng);
        Debris_Spawn_Independent(this, this, MODEL_DEBRIS_FLESH, TEXTURE_DEBRIS_FLESH , 0, 0, 0, 0, m_fStretch*0.33f, 
          plPos, vSpeed*70.0f, aAng);                
        break;
      }
    }
 
    // notify possible targets of end of the death sequence
    if (m_penExplodeDeathTarget!=NULL) {
      SendToTarget(m_penExplodeDeathTarget, EET_TRIGGER, m_penKiller);
    }

    // turn off collision and physics
    //SetPhysicsFlags(EPF_MODEL_IMMATERIAL);
    //SetCollisionFlags(ECF_IMMATERIAL);

    PlaySound(m_soSound, SOUND_CHIMES, SOF_3D);

    m_iIndex = 20;
    while ((m_iIndex--)>1) {
      CPlacement3D plExplosion;
      plExplosion.pl_OrientationAngle = ANGLE3D(0.0f, 0.0f, 0.0f);
      plExplosion.pl_PositionVector = FLOAT3D(0.3f+FRnd()*0.1f, 1.0f+FRnd()*0.5f, 0.3f+FRnd()*0.1f)*m_fStretch;
      plExplosion.pl_PositionVector += GetPlacement().pl_PositionVector;
      ESpawnEffect eSpawnEffect;
      eSpawnEffect.colMuliplier = C_WHITE|CT_OPAQUE;
      eSpawnEffect.betType = BET_CANNON;
      FLOAT fSize = (m_fStretch*m_iIndex)*0.333f;
      eSpawnEffect.vStretch = FLOAT3D(fSize, fSize, fSize);
      CEntityPointer penExplosion = CreateEntity(plExplosion, CLASS_BASIC_EFFECT);
      penExplosion->Initialize(eSpawnEffect);

      ShakeItBaby(_pTimer->CurrentTick(), m_iIndex/4.0f, FALSE);

      autowait(0.05f + FRnd()*0.2f);
    }

    autowait(m_fDeathDuration);

    // notify possible targets of end of the death sequence
    if (m_penEndDeathTarget!=NULL) {
      SendToTarget(m_penEndDeathTarget, EET_TRIGGER, m_penKiller);
    }

    EDeath eDeath;
    eDeath.eLastDamage.penInflictor = m_penDeathInflictor;
    jump CEnemyBase::Die(eDeath);
  }

  TeleportToDeathMarker(EVoid)
  {

    m_bInvulnerable = TRUE;

    StartModelAnim(SUMMONER_ANIM_VANISHING, SOF_SMOOTHCHANGE);
    autowait(GetModelObject()->GetAnimLength(SUMMONER_ANIM_VANISHING));

    // hide model
    DisappearEffect();
    SwitchToEditorModel();
    SetCollisionFlags(ECF_IMMATERIAL);
    
    // destroy possible flames
    CEntityPointer penFlame = GetChildOfClass("Flame");
    if (penFlame!=NULL) {
      penFlame->Destroy();
    }

    // wait a little bit
    autowait(2.0f);

    CPlacement3D pl;
    pl.pl_PositionVector = m_penDeathMarker->GetPlacement().pl_PositionVector;
    FLOAT3D vToPlayer;
    if (m_penEnemy!=NULL) {
      vToPlayer = m_penEnemy->GetPlacement().pl_PositionVector - pl.pl_PositionVector;
    } else {
      vToPlayer = m_vPlayerSpotted - pl.pl_PositionVector;
    }
    vToPlayer.Normalize();
    DirectionVectorToAngles(vToPlayer, pl.pl_OrientationAngle);
    Teleport(pl);

    // show model
    SpawnTeleportEffect();

    autowait(0.5f);
    SwitchToModel();
    SetCollisionFlags(ECF_MODEL);
    
    m_bInvulnerable = FALSE;

    StartModelAnim(SUMMONER_ANIM_APPEARING, SOF_SMOOTHCHANGE);
    autowait(GetModelObject()->GetAnimLength(SUMMONER_ANIM_APPEARING));

    return EReturn();
  }

  BossAppear(EVoid)
  {
    return EReturn();
  }

  // overridable called before main enemy loop actually begins
  PreMainLoop(EVoid) : CEnemyBase::PreMainLoop
  {
    autocall BossAppear() EReturn;
    return EReturn();
  }

  Immaterial() {
    
    // hide model
    DisappearEffect();
    SwitchToEditorModel();
    SetCollisionFlags(ECF_IMMATERIAL);
    
    // destroy possible flames
    CEntityPointer penFlame = GetChildOfClass("Flame");
    if (penFlame!=NULL) {
      penFlame->Destroy();
    }

    // wait required time
    autowait(m_fImmaterialDuration+FRnd()*2.0f-1.0f);
    
    INDEX iMaxTries = 10;
    FLOAT3D vTarget;
    // move to a new position
    do {
      CSummonerMarker *marker = &((CSummonerMarker &)*m_penTeleportMarker);
      INDEX iMarker = IRnd()%m_iTeleportMarkers;
      while (iMarker>0) {
        marker = &((CSummonerMarker &)*marker->m_penTarget);
        iMarker--;
      }
      vTarget = marker->GetPlacement().pl_PositionVector;
      FLOAT fR = FRnd()*marker->m_fMarkerRange;
      FLOAT fA = FRnd()*360.0f;
      vTarget += FLOAT3D(CosFast(fA)*fR, 0.05f, SinFast(fA)*fR);
    } while (!DistanceToAllPlayersGreaterThen(1.0f) || (iMaxTries--)<1);
    
    CPlacement3D pl;
    pl.pl_PositionVector = vTarget;
    FLOAT3D vToPlayer;
    if (m_penEnemy!=NULL) {
      vToPlayer = m_penEnemy->GetPlacement().pl_PositionVector - vTarget;
    } else {
      vToPlayer = m_vPlayerSpotted - vTarget;
    }
    vToPlayer.Normalize();
    DirectionVectorToAngles(vToPlayer, pl.pl_OrientationAngle);
    Teleport(pl);

    // show model
    SpawnTeleportEffect();
    SwitchToModel();
    SetCollisionFlags(ECF_MODEL);
    
    m_bShouldTeleport = FALSE;
    m_tmMaterializationTime = _pTimer->CurrentTick();
    m_bFiredThisTurn = FALSE;

    m_bInvulnerable = FALSE;
    
    PlaySound(m_soTeleport, SOUND_MATERIALIZE, SOF_3D);

    StartModelAnim(SUMMONER_ANIM_APPEARING, SOF_SMOOTHCHANGE);
    autowait(GetModelObject()->GetAnimLength(SUMMONER_ANIM_APPEARING));

    SendEvent(EBegin());
    return EReturn();

  }

  SummonerLoop() {
    // spawn a 1sec reminder
    SpawnReminder(this, 1.0f, 128);
    wait () {
      on (EBegin) :
      {
        call CEnemyBase::MainLoop();
      }
      on (EReminder er) :
      {
        // pass all reminders but the 128 one
        if (er.iValue==128) {
          RecalculateFuss();
          // see if we have to teleport
          if (_pTimer->CurrentTick()>m_tmMaterializationTime+m_fCorporealDuration) {
            m_bShouldTeleport = TRUE;
          }
          // spawn the reminder again so that we return here
          SpawnReminder(this, 1.0f, 128);
        } else if (er.iValue==129 && !m_bDying) {
          call InitiateTeleport();
        } else if (TRUE) {
          pass;
        }
        resume;
      }      
      // we want to teleport in near future
      on (ESummonerTeleport est) :
      {
        //m_fTeleportWaitTime = est.fWait;
        SpawnReminder(this, est.fWait, 129);
        resume;
      }
      otherwise () : {
        resume;
      }
    }
  }

/************************************************************
 *                       M  A  I  N                         *
 ************************************************************/
  Main(EVoid) {
    
    // declare yourself as a model
    InitAsEditorModel();
    
    SetPhysicsFlags(EPF_MODEL_WALKING);
    SetCollisionFlags(ECF_IMMATERIAL);
    SetFlags(GetFlags()|ENF_ALIVE);
    
    en_fDensity = 10000.0f;
    m_fDamageWounded = 1e6f;
    
    m_sptType = SPT_BLOOD;
    m_bBoss = TRUE;
    SetHealth(SUMMONER_HEALTH);
    m_fMaxHealth = SUMMONER_HEALTH;
    m_fBodyParts = 0;
    // setup moving speed
    m_fWalkSpeed = 0.0f;
    m_aWalkRotateSpeed = AngleDeg(270.0f);
    m_fAttackRunSpeed = 0.0f;
    m_aAttackRotateSpeed = AngleDeg(270.0f);
    m_fCloseRunSpeed = 0.0f;
    m_aCloseRotateSpeed = AngleDeg(270.0f);
    // setup attack distances
    m_fAttackDistance = 500.0f;
    m_fCloseDistance = 50.0f;
    m_fStopDistance = 500.0f;
    m_fIgnoreRange = 600.0f;
    m_iScore = 1000000;
    // setup attack times
    m_fAttackFireTime = m_fFirePeriod;
    m_fCloseFireTime = m_fFirePeriod;
    
    SetPhysicsFlags(EPF_MODEL_WALKING);
    StandingAnim();

    // set your appearance
    //m_fStretch = SIZE;
    SetComponents(this, *GetModelObject(), MODEL_SUMMONER, TEXTURE_SUMMONER, 0, 0, 0); 
    AddAttachmentToModel(this, *GetModelObject(), SUMMONER_ATTACHMENT_STAFF, MODEL_STAFF, TEXTURE_STAFF, 0, 0, 0);
    GetModelObject()->StretchModel(FLOAT3D(m_fStretch, m_fStretch, m_fStretch ));
    ModelChangeNotify();

    AddToMovers();

    autowait(_pTimer->TickQuantum);

    m_emEmiter.Initialize(this);
    m_emEmiter.em_etType=ET_SUMMONER_STAFF;

    // count templates by groups
    INDEX i;
    CEntityPointer *pen;
    m_iGroup01Count = 0;
    pen = &m_penGroup01Template01;
    for (i=0; i<SUMMONER_TEMP_PER_GROUP; i++) {
      if (&*pen[i]!=NULL) { m_iGroup01Count++; }
    }
    m_iGroup02Count = 0;
    pen = &m_penGroup02Template01;
    for (i=0; i<SUMMONER_TEMP_PER_GROUP; i++) {
      if (&*pen[i]!=NULL) { m_iGroup02Count++; }
    }
    m_iGroup03Count = 0;
    pen = &m_penGroup03Template01;
    for (i=0; i<SUMMONER_TEMP_PER_GROUP; i++) {
      if (&*pen[i]!=NULL) { m_iGroup03Count++; }
    }

    if (!DoSafetyChecks()) {
      Destroy();
      return;
    }
   
    // count spawn markers
    CEnemyMarker *it;
    m_iSpawnMarkers = 1;
    it = &((CEnemyMarker &)*m_penSpawnMarker);
    while (it->m_penTarget!=NULL)
    {
      it = &((CEnemyMarker &)*it->m_penTarget);
      m_iSpawnMarkers ++;
    }

    // count teleport markers
    m_iTeleportMarkers = 1;
    it = &((CEnemyMarker &)*m_penTeleportMarker);
    while (it->m_penTarget!=NULL)
    {
      it = &((CEnemyMarker &)*it->m_penTarget);
      m_iTeleportMarkers ++;
    }

    m_iSpawnScheme = 0;
    m_fMaxCurrentFuss = m_fMaxBeginFuss;
    m_bDying = FALSE;
    m_tmDeathBegin = 0.0f;
    m_fDeathDuration = 0.0f;
    m_bInvulnerable = TRUE;
    m_bExploded = FALSE;

    // wait to be triggered
    wait() {
      on (EBegin) : { resume; }
      on (ETrigger) : { stop; }
      otherwise (): { resume; }
    }

    m_soExplosion.Set3DParameters(1500.0f, 1000.0f, 2.0f, 1.0f);
    m_soSound.Set3DParameters(1500.0f, 1000.0f, 2.0f, 1.0f);
    m_soChant.Set3DParameters(1500.0f, 1000.0f, 2.0f, 1.0f);
    m_soTeleport.Set3DParameters(1500.0f, 1000.0f, 3.0f, 1.0f);
    m_iTaunt = 0;

    //PlaySound(m_soSound, SOUND_APPEAR, SOF_3D);
    // teleport in
    SpawnTeleportEffect();
    SwitchToModel();
    m_bInvulnerable = FALSE;
    SetCollisionFlags(ECF_MODEL);

    PlaySound(m_soTeleport, SOUND_MATERIALIZE, SOF_3D);

    StartModelAnim(SUMMONER_ANIM_APPEARING, SOF_SMOOTHCHANGE);
    autowait(GetModelObject()->GetAnimLength(SUMMONER_ANIM_APPEARING));

    m_tmMaterializationTime = _pTimer->CurrentTick();
    
    // one state under base class to intercept some events
    jump SummonerLoop();
    //jump CEnemyBase::MainLoop();
  };
};